二、接口
接口
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. 接口继承类
接口可以继承类类型,接口同样继承类的private与protected成员
这意味着 : 实现该接口的类必须也同时实现接口上的继承的类
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
}