接口
这篇文章是对文档的整理
TypeScript 的核心原则之一是对值所具有的结构进行类型检查。 而接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
function printLabel(labelledObj: { label: string }) {
console.log(labelledObj.label);
}
let myObj = { size: 10, label: "Size 10 Object" };
printLabel(myObj);
printLabel
有一个参数,并要求这个对象参数有一个名为label
类型为string
的属性。
需要注意的是,我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些必需的属性是否存在,并且其类型是否匹配。 然而,有些时候TypeScript却并不会这么宽松,我们下面会稍做讲解。
类型检查器不会去检查属性的顺序,只要相应的属性存在并且类型也是对的就可以。
总结:
定义 接口的类型(即通过接口实现类型) 的变量 必须完全符合对应类型的属性和方法
函数中对传入参数的类型检查,只检查必需的属性,多或少无所谓
可选属性
interface SquareConfig {
color?: string;
width?: number;
}
带有可选属性的接口与普通的接口定义差不多,只是在可选属性名字定义的后面加一个?
符号
好处:
- 可以对可能存在的属性进行预定义。
- 可以捕获引用了不存在的属性时的错误。
例子:
我们故意将createSquare
里的color
属性名拼错,就会得到一个错误提示:
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
let newSquare = {color: "white", area: 100};
if (config.color) {
// Error: Property 'clor' does not exist on type 'SquareConfig'
newSquare.color = config.clor;
}
if (config.width) {
newSquare.area = config.width * config.width;
}
return newSquare;
}
let mySquare = createSquare({color: "black"});
只读属性
一些对象属性只能在对象刚刚创建的时候修改其值。 你可以在属性名前用readonly
来指定只读属性:
interface Point {
readonly x: number;
readonly y: number;
}
TypeScript具有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 与 const
最简单判断该用readonly
还是const
的方法:
看要把它做为变量使用还是做为一个属性。
做为变量使用的话用
const
,若做为属性则使用readonly
。
对象字面量会被特殊对待而且会经过额外属性检查,当将它们赋值给变量或作为参数传递的时候。 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
// ...
}
let mySquare = createSquare({ colour: "red", width: 100 });
所以会以下出现:error: 'colour' not expected in type 'SquareConfig'
绕过属性额外检查:
-
使用类型断言
interface SquareConfig { color?:string, width:number } function createSquare(config:SquareConfig) { return config } let mySquare = createSquare({colour:"red",width:40} as SquareConfig) console.log(mySquare)
-
添加一个字符串索引签名
interface SquareConfig { color?: string; width?: number; [propName: string]: any; }
前提:能够确定这个对象可能具有某些做为特殊用途使用的额外属性。
可索引类型
与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]
或ageMap["daniel"]
。 可索引类型具有一个索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。 让我们看一个例子:
interface StringArray {
[index: number]: string;
}
let myArray: StringArray;
myArray = ["Bob", "Fred"];
let myStr: string = myArray[0];
上面例子里,我们定义了StringArray
接口,它具有索引签名。 这个索引签名表示了当用number
去索引StringArray
时会得到string
类型的返回值。
共有支持两种索引签名:字符串和数字。
可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型。
数字索引可能出现的问题
这是因为当使用
number
来索引时,JavaScript会将它转换成string
然后再去索引对象。 也就是说用100
(一个number
)去索引等同于使用"100"
(一个string
)去索引,因此两者需要保持一致。
class Animal {
name: string;
}
class Dog extends Animal {
breed: string;
}
// 错误:使用'string'索引,有时会得到Animal!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
它们也会确保所有属性与其返回值类型相匹配。 因为字符串索引声明了obj.property
和obj["property"]
两种形式都可以。
interface NumberDictionary {
[index: string]: number;
length: number; // 可以,length是number类型
name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
}
这是因为在接口的设置中,设置的索引标签为字符串,最后输出数字类型
但是,name属性是一个字符串类型,不能通过索引得到
你可以将索引签名设置为只读,这样就防止了给索引赋值:
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
你不能设置myArray[2]
,因为索引签名是只读的。
函数类型
interface Girl {
say():string
}
定义的接口中有函数类型,并且函数的返回值是一个字符串
类 类型
TypeScript也能够用它来明确的强制一个类去符合某种契约——简而言之,就是接口中存在的属性或方法,那么类中必须同样拥有,否则将会报错。
通过implements
进行约束
你也可以在接口中描述一个方法,在类里实现它,如同下面的setTime
方法一样:
interface ClockInterface {
currentTime: Date;
setTime(d: Date);
}
class Clock implements ClockInterface {
currentTime: Date;
setTime(d: Date) {
this.currentTime = d;
}
constructor(h: number, m: number) { }
}
继承
interface Girl {
say():string
}
interface Teacher extends Girl {
name:string
}