接口可以用来约束对象、函数以及类的结构和类型,这是一种代码协作的契约,调用该接口时必须遵守,不能改变
ts与其它语言不同的是,并没有 “实现” 接口这个概念,而是只关注值的外形,只要传入的对象满足上面的必要条件,那么它就是被允许的。这就是所谓的“鸭式辩型法”(像鸭子一样走路、游泳和嘎嘎叫的鸟就是鸭子)
1、对象类型接口
interface List { id: number; name: string; } interface Result { data: List[]; } function getResult(result: Result) { result.data.forEach(item => { console.log(item.id, item.name); }) } let myResult = { data: [ {id: 1, name: 'Bob', score: 98}, {id: 2, name: 'Carl', score: 87} ] }; getResult(myResult); // 1 'Bob' // 2 'Carl'
类型检查器会查看 getResult 的调用,其传入的参数中包含了三个属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配
然而,有些时候ts却并不会这么宽松,例如:getResult 参数传入的是对象字面量,ts就会对额外的参数进行类型检查
getResult({ data: [{id: 1, name: 'Bob', score: 98}, {id: 2, name: 'Carl', score: 87}] }); // error TS2322: Type '{ id: number; name: string; score: number; }' is not assignable to type 'List'. // Object literal may only specify known properties, and 'score' does not exist in type 'List'.
绕过这种检查的方法有四种:
(1)、把对象字面量赋值给一个变量(如前面的例子中的变量 myResult)
(2)、使用类型断言
(3)、可选属性:带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?符号
(4)、字符串索引签名
// 使用类型断言 getResult({data: [{id: 1, name: 'Bob'}, {id: 2, name: 'Carl', score: 98}]} as Result) // 可选属性 interface List { id: number; name: string; score?: number; // 可选属性 } // 字符串索引签名 interface List { id: number; name: string; [propName: string]: any; // 字符串索引签名 } getResult({ data: [{id: 1, name: 'Bob', score: 98}, {id: 2, name: 'Carl', score: 87}] }); // 1 'Bob' // 2 'Carl'
1-1、可索引类型
可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。比如:a[0] 或 a["name"]
索引签名可以是字符串和数字,也可以同时使用两种类型的索引
// 数字索引 interface StringArray { [inder: number]: string; } let s1: StringArray = ["TypeScript", "Webpack"]; console.log('s1: ', s1); // s1: [ 'TypeScript', 'Webpack' ] // 字符串索引 interface ScoreMap { [subject: string]: number; } let s2: ScoreMap = { "Chinese": 99, "Math": 100 } console.log('s2: ', s2); // s2: { Chinese: 99, Math: 100 } // 同时使用字符串和数字索引 interface StudentMap { [index: number]: string; [name: string]: string; } let s3: StudentMap[] = [ { 1: "678分", "姓名": "张伟" }, { 2: "670分", "姓名": "尔康" } ] console.log('s3: ', s3); // s3: [ { '1': '678分', '姓名': '张伟' }, { '2': '670分', '姓名': '尔康' } ]
注意:如果同时使用字符串索引和数字索引,数字索引的返回值必须是字符串索引返回值类型的子类型。因为当使用number来索引时,js会将它隐式转换成string,然后再去索引对象。
class Animal { name: string; } class Dog extends Animal { breed: string; } interface Okay { [x: string]: Animal; [y: number]: Dog; } // Numeric index type 'Animal' is not assignable to string index type 'Dog'. interface NotOkay { [x: string]: Dog; [y: number]: Animal; // 数字索引类型“Animal”不能赋给字符串索引类型“Dog” }
1-2、只读属性
可以在属性名前用 readonly 来指定只读属性
interface Point { readonly x: number; readonly y: number; } // 可以通过赋值一个对象字面量来构造一个Point。 赋值后,x和y再也不能被改变了。 let p: Point = { x: 3, y: 5}; console.log('p', p); // p { x: 3, y: 5 } // p.x = 20; // Cannot assign to 'x' because it is a read-only property
ts 具有 ReadonlyArray<T> 类型,它与 Array<T> 相似,只是把所有可变方法都去掉了。可以确保数组创建后就再也不能修改
let arr: number[] = [1, 2, 3]; let ro: ReadonlyArray<number> = arr; ro[0] = 33; // 类型“readonly number[]”中的索引签名仅允许读取 ro.push(4); // 类型“readonly number[]”上不存在属性“push” ro.length = 99; // Cannot assign to 'length' because it is a read-only property
判断该使用readonly 还是 const 的方法主要是看作为变量还是作为属性,作为变量的话使用 const,作为属性则使用 readonly
2、函数类型接口
它就像是一个只有 参数列表 和 返回值类型 的函数定义,参数列表里的每个参数都需要名字和类型
interface Add { (base: number, increment: number): number } // 调用接口 let add: Add = (x: number, y: number) => x + y; console.log( add(1, 2) ); // 3
3、类类型接口
ts中的类可以像Java里的接口一样,使用类来实现一个接口,由此来明确的强制一个类去符合某种契约。
interface ClockInterface {
currentTime: Date;
}
class Clock implements ClockInterface {
currentTime: Date;
constructor(h: number, m: number) {}
}
也可以在接口中描述一个方法,在类里实现它
interface ClockInterface { currentTime: Date; setTime(d: Date): } class Clock implements ClockInterface { currentTime: Date; setTime(d: Date) { this.currentTime = d; } constructor(h: number, m: number) {} }
4、混合类型接口
同时使用 对象类型接口 和 函数类型接口
interface Lib { (): void; version: string; doSomething(): void; } function getLib() { let lib = (() => {}) as Lib; lib.version = '0.01'; lib.doSomething = () => console.log('hello world!'); return lib; } let l1 = getLib(); console.log( l1() ); // undefined let l2 = getLib(); l2.doSomething(); // hello world!
5、继承接口
接口也可以相互继承,可以从一个接口里复制成员到另一个接口里,由此可以更加灵活的将接口分割到可重用的模块里
interface Shape { color: string; } interface Square extends Shape { sideLength: number; } let square: Square = { color: 'red', sideLength: 15 } console.log('square ', square); // square { color: 'red', sideLength: 15 }
一个接口可以继承多个接口,创建出多个接口的合成接口
interface Shape { color: string; } interface PenStroke { penWidth: number } interface Square extends Shape, PenStroke { sideLength: number; } let square: Square = { color: 'red', penWidth: 6, sideLength: 15 } console.log('square ', square); // square { color: 'red', penWidth: 6, sideLength: 15 }