基本使用
-
ts的接口好比一个名字,用来描述对象的类型要求,通过
interface
关键字定义interface LabelledValue { label: string; } // 通过接口定义函数参数类型要求 function printLabel(labelledObj: LabelledValue) { console.log(labelledObj.label); } let myObj = {size: 10, label: "Size 10 Object"}; printLabel(myObj);
-
直接通过接口定义的属性都是必须的,对于可选属性可通过
?:
定义,类似于js中的?.
interface SquareConfig { color: string; // 必传 width?: number; // 非必传 } function createSquare(config: SquareConfig): {color: string; area: number} { let newSquare = {color: "white", area: 100}; newSquare.color = config.color; if (config.width) { newSquare.area = config.width * config.width; } return newSquare; }
-
当希望对象的属性不可修改时,可用
readonly
关键字设置为只读interface Point { readonly x: number; y: number; } let p1: Point = { x: 10, y: 20 }; // 初始赋值 p1.x = 5; // error! x is a read-only property p1.y = 30 // ok!
-
数组对象可通过
ReadonlyArray
设置只读// 可将普通数组修改为只读数组 let a: number[] = [1, 2, 3, 4]; let ro: ReadonlyArray<number> = a; ro[0] = 12; // error! ro.push(5); // error! // 但只读数组不能通过赋值的方式修改为普通数组 let a2: number[] = ro // error! // 可用断言的方式重新设置为普通数组 let a3: number[] = ro as number[] // ok
最简单判断该用
readonly
还是const
的方法是看要把它做为变量使用还是做为一个属性。 做为变量使用的话用const
,若做为属性则使用readonly
-
当我们给函数传入接口未定义的额外属性时,ts会报错
// error: 'other' not expected in type 'SquareConfig' let mySquare = createSquare({ other: "demo", color: "white" });
使用断言可以避开额外属性检查
let mySquare = createSquare({ other: "demo", color: "white" } as SquareConfig);
但是,最佳的方式是在接口上添加一个字符串索引签名
interface SquareConfig { color: string; width?: number; [propName: string]: any; // 表示除了上面定义的color 和 width, SquareConfig 还可以带有任意数量的字符串属性,这些属性的返回值可以是任意类型 }
还有一种跳过检查的方式,就是将参数先赋值给另一个变量,通过这种方式不会进行 额外属性的检查
const tempObj = { other: "demo", color: "white" } let mySquare = createSquare(tempObj);
函数类型
除了描述带有属性的普通对象外,接口也可以描述函数类型
-
我们需要给接口定义一个调用签名。 它就像是一个只有参数列表和返回值类型的函数定义。参数列表里的每个参数都需要名字和类型。
interface SearchFunc { // 参数类型 // 返回值类型 (source: string, subString: string): boolean; }
-
调用的时候函数的参数名不需要与接口里定义的名字相匹配,且不需要显式的指定参数类型,因为函数直接赋值给了
SearchFunc
类型变量,ts会对参数进行逐个检查并自动判断参数类型和返回值类型const testFn:SearchFunc = (src, sub) => { console.log(src) console.log(sub) return true }
可索引的类型
用于描述那些能够“通过索引得到”的类型,如
a[10]
ageMap["daniel"]
-
可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型
interface StringArray { [index: number]: string; } let myArray: StringArray; myArray = ["Bob", "Fred"]; let myStr: string = myArray[0];
TypeScript支持两种索引签名:字符串和数字。 可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。 这是因为当使用 number来索引时,JavaScript会将它转换成string然后再去索引对象。 也就是说用 100(一个number)去索引等同于使用"100"(一个string)去索引,因此两者需要保持一致
-
索引签名也可以设置为只读
interface ReadonlyStringArray { readonly [index: number]: string; } let myArray: ReadonlyStringArray = ["Alice", "Bob"]; myArray[2] = "Mallory"; // error!
类类型
与C#或Java里接口的基本作用一样,TypeScript也能够用它来明确的强制一个类去符合某种契约
-
接口描述了类的公共部分,而不是公共和私有两部分。 它不会帮你检查类是否具有某些私有成员
interface ClockInterface { currentTime: Date; setTime(d: Date); } class Clock implements ClockInterface { currentTime: Date; setTime(d: Date) { this.currentTime = d; } constructor(h: number, m: number) { } }
类是具有两个类型的:静态部分的类型和实例的类型;
当一个类实现了一个接口时,只对其实例部分进行类型检查。 constructor存在于类的静态部分,所以不在检查的范围内
接口继承
-
接口可以相互继承复用
interface Shape { color: string; } interface PenStroke { penWidth: number; } interface Square extends Shape, PenStroke { sideLength: number; } let square = <Square>{}; square.color = "blue"; square.sideLength = 10; square.penWidth = 5.0;
混合类型
-
一个对象可以同时做为函数和对象使用,并带有额外的属性
interface Counter { (start: number): string; interval: number; reset(): void; } function getCounter(): Counter { let counter = <Counter>function (start: number) { }; counter.interval = 123; counter.reset = function () { }; return counter; } let c = getCounter(); c(10); c.reset(); c.interval = 5.0;
接口继承类
这一节还不太理解,节选ts中文文档原文
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。 就好像接口声明了所有类中存在的成员,但并没有提供具体实现一样。 接口同样会继承到类的private和protected成员。 这意味着当你创建了一个接口继承了一个拥有私有或受保护的成员的类时,这个接口类型只能被这个类或其子类所实现(implement)。
当你有一个庞大的继承结构时这很有用,但要指出的是你的代码只在子类拥有特定属性时起作用。 这个子类除了继承至基类外与基类没有任何关系。 例:
class Control {
private state: any;
}
interface SelectableControl extends Control {
select(): void;
}
class Button extends Control implements SelectableControl {
select() { }
}
class TextBox extends Control {
select() { }
}
// 错误:“Image”类型缺少“state”属性。
class Image implements SelectableControl {
select() { }
}
class Location {
}
- 在上面的例子里,
SelectableControl
包含了Control
的所有成员,包括私有成员state
。 因为state
是私有成员,所以只能够是Control
的子类们才能实现SelectableControl
接口。 因为只有Control
的子类才能够拥有一个声明于Control
的私有成员state
,这对私有成员的兼容性是必需的。 - 在
Control
类内部,是允许通过SelectableControl
的实例来访问私有成员state
的。 实际上,SelectableControl
接口和拥有select
方法的Control
类是一样的。Button
和TextBox
类是SelectableControl
的子类(因为它们都继承自Control
并有select
方法),但Image
和Location
类并不是这样的。