二、接口

2020-02-19  本文已影响0人  zdxhxh

接口

TypeScript 核心原则之一是对值所具有的结构进行类型检查。

它有时会称为“鸭式辨型法” 或 “结构性子类型法”

接口的作用就是为类或者变量加约束。

示例

下面来看接口是如何工作的。

function printLabel(labelledObj : {label : string}) { 
  console.log(labelledObj.label)
}
let myObj = { size: 10, label: 'Size 10 Object' }
printLabel(myObj)

类型检查器会查看printLabel的入参,我们要求为labelledObj的这个形参定义了一个名为label类型为string的属性

我们为该方法传入一个对象,这个对象包含很多属性,但是编译器只会检查那些必须的属性是否存在,以及类型是否匹配。

我们可以通过接口描述该参数。

interface LabelValue { 
  label : string 
} 
function printValue(labelValue : LabelValue) { 
  console.log(labelValue.label)
}

注意 : 我们并不像其他语言一样,说传给printLabel的对象实现了这个接口,我们只会关注值得外形,只要传入的对象满足上述的必要条件,则它就是被允许的。

另外,类型检查器不会去检查属性的顺序

1. 可选属性

接口中的属性不全都是必须的,有些只在某些条件下存在,或者不存在。可选属性的好处在于预定义,并且捕获引用不存在的属性错误。

interface Square {
  color ?: string // 表示该属性是可选的
  area: number
}

2. 只读属性

一些属性只能在对象刚刚创建的时候修改它的值,你可以在属性名前用readonly来指定只读属性。

interface Point{ 
  readonly x : number
  readonly y : number 
}

你可以通过赋值一个对象字面量来构造一个point,然后只读属性的值就再也不能改变了

let p1 : Point = { 
  x : 10,
  y : 10
}
p1.x = 5 // error 

对于数组,可以使用ReadonlyArray<T>类型,与Array<T>相似,只是把所有的可变方法去掉。确保数组不可修改

let a : number[] = [1,2,3,4]
let ro : ReadonlyArray<number> = a 
ro[0] = 12  // error 
ro.push(5)  // error
ro.length = 100 // error 
a = ro // error

上面最后一段代码,你想把整个ReadonlyArray赋值给一个普通数组也是不行的,但是你可以使用类型断言

a = ro as number[]

readonly vs const

最简单的方法是把它作为变量还是属性,如果是变量则使用const,若为属性则使用readonly

3. 额外属性检查

在我们定义了一个含有可选属性的接口并约束函数的入参类型后,但是没有书写符合该接口的对象字面量传入,此时对象字面量会进行额外检查。

interface Point {
  x ?: number
  y ?: number 
}
function plus(point : Point) {
  return point.x + point.y
}
plus({x : 10,z : 15}) // 尽管x是兼容接口的,但是对象字面会进行额外属性检查,如果存在目标类型不包含的属性如z时,会得到一个错误。

我们可以通过索引签名解决这个问题

interface Point {
  x ?: number
  y ?: number 
  [propName: string]: any
}

4. 函数类型

接口可以描述Javascript对象的各种类型此外,还可以描述函数类型。

为了使用接口表示函数类型,我们需要给接口一个调用签名。他就像是一个参数列表和返回值类型的函数类型。参数列表中的每个参数都需要名字和类型。

interface SearchFunc { 
  (source : string,subString : string) :  boolean
}

这样就定义了一个函数类型的接口,如下:

let mySearch : SearchFunc
mySearch = function(source : string,subString : string) : boolean { 
  let result = source.search(subString);
  return result > -1 
}

对于函数类型的接口而言,函数的参数名不需要与接口的名字相匹配,如 :

let mySearch : SearchFunc
mySearch = function(src : string,str : string) : boolean { 
  let result = src.search(str);
  return result > -1 
}

另外,对已声明该接口的变量,实现的函数的形参可以不声明对应的类型,类型系统可以正确的推断参数类型。

let mySearch : SearchFunc 
mySearch = function(src,str) : boolean { 
  let result = src.search(str);
  return result > -1 
}

5. 可索引的类型

通过索引得到的类型,比如a[10]或者ageMap['daniel'],可索引类型具有一个索引签名,描述了对象索引的类型。

interface StringArray { 
  [index : number] : string 
}
let myArray : StringArray 
myArray = ['Bod','Fred']
let myStr: string = myArray[0]

TypeScript 支持两种索引签名:字符串和数字。

可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型

这里我也没看明白

class Parent {
  name: string
}
class Child extends Parent {
  breed: string
}

// 错误:使用数值型的字符串索引,有时会得到完全不同的Parent!
interface NotOkay {
  [x: number]: Parent
  [x: string]: Child
}

字符串索引签名很好的描述了dirctionay模式,确保所有属性与返回值类型相匹配,你可能见过其他类型语言,如dart

Map<String, dynamic> data = new Map<String, dynamic>();

6. 类类型

实现接口

可以使用implements关键字为类实现接口

interface AImpl { 
  name : string 
  setTime(d : Date)
}
class A implements AImpl {
  name : string 
  constructor(name : string) { 
    this.name = name 
  }
  setTime(d : Date) { 
    return ''
  }
}

类接口描述了类的公共部分,而不是公共与私有两部分,他不会检查类是否具有某些私有成员

7. 类静态部分和实例部分的区别

接上,类接口描述了类的公共部分,而不是公共与私有两部分,他不会检查类是否具有某些私有成员

当试图去使用类接口定义一个类实现它的静态部分时,会出现错误。

interface AInterface { 
  new (name : string)
  name : string 
}
class A implements AInterface {
  name : string 
  constructor(name : string) { 
    this.name = name 
  }
}

那如果现在需要你为一个类的构造函数实现类似上面的约束,可以这样做。

interface AConstructor { 
  new (name : string) : AInterface
}
interface AInterface { 
  name : string 
}
class A implements AInterface{ 
  name : string 
  constructor(name : string) { 
    this.name = name 
  }
}
function createA(ctor :AConstructor,name : string ):AInterface { 
  return new ctor(name)
}

let a = createA(A,'药水哥')

8. 接口继承

和类一样,接口也可以相互继承,这让我们能够从一个接口里复制成员到另一个接口,可以更灵活地将接口分割到可复用的模块

interface Shape {
  color: string
}

interface Square extends Shape {
  sideLength: number
}
let square = {} as Square
square.color = 'blue'
square.sideLength = 10

与类不一样的是,接口可以多继承

interface B {
  name : string
}
interface A { 
  age : number
}
interface C extends B,A { 
  school : string
}

9. 混合类型

我们知道在JavaScript中,函数对象其实是具有[[call]]属性的对象,所以一个对象可以同时作为函数和对象使用。

我们可以这样做

interface Counter {
  interval: number
}
function getCounter() : Counter { 
  let counter = (function (start: number) { console.log(start) }) as Counter
  counter.interval = 123
  return counter
}

let c = getCounter()
c(10)
c.interval = 5.0

10. 接口继承类

接口可以继承类类型,接口同样继承类的privateprotected成员

这意味着 : 实现该接口的类必须也同时实现接口上的继承的类

class Person { 
  private name : string 
}
interface StudentInterface extends Person { 
  study() : void 
}
// 没问题
class Student extends Person{ 
  study() : void 
}
// 没问题
class Student extends Person implements StudentInterface{ 
  study() : void 
}

// Error : "Student" 类型缺少name属性
class Student implements StudentInterface{ 
  study() : void 
}
上一篇 下一篇

猜你喜欢

热点阅读