目录
说明
本文学习自掘金大神的文章https://juejin.cn/post/7003171767560716302,原作者是伟大的兔神。
接口
对类型的格式进行规定和约束。
原文中提到了鸭式辨型法,即,具有传入的对象拥有要求的属性就认为它符合要求。
举个例子:
function getName(obj: { name: string }) {
return obj.name;
}
// console.log(getName({ name: 'Jack', age: 11 }));//报错,因为多出了一个age属性
// console.log(getName({ name: 'Jack' }));//正确
let myObj = { name: 'Jack', age: 123 };
console.log(getName(myObj));//正确
发现,调用函数时直接传入参数,要求字段必须完全符合,不能多也不能少。
而如果先定义对象变量,再调用函数传入变量,则可以多出一些属性了。这就说明,赋值使得类型检查变得宽松了。因为在赋值的时候,myObj会自动推断出所赋值的类型,相当于:
let myObj: { name: string, age: number } = { name: 'Jack', age: 123 };
然后调用函数时,将myObj赋给obj,这时就会采用鸭式辨型法,两者都拥有name属性,于是认为是相同类型了。
再使用接口写一下:
interface IObj {
name: string
}
function getName(obj: IObj) {
return obj.name;
}
// console.log(getName({ name: 'Jack', age: 11 }));//报错,因为多出了一个age属性
// console.log(getName({ name: 'Jack' }));//正确
let obj = { name: 'Jack', age: 123 };
console.log(getName(obj));//正确
一样的效果。
注意,在 type、interface 中可以使用逗号、分号,class 中不能用逗号。
可选属性
在接口中可以定义一些可选的属性。
interface IPerson {
id: number;
name: string;
age?: number;
gender: string;
}
只读属性
有些属性是只读的,不能修改,只能在对象刚刚创建的时候修改其值。
interface IPerson {
readonly id: number;
name: string;
age?: number;
gender: string;
}
let p: IPerson = { id: 1, name: 'Tom', gender: 'M' }
// p.id = 2;//报错
与const不同,对于引用类型,const只能保证用户不能修改引用地址,但内部的属性是可以修改的。但readonly修饰的属性,内部所有数据均不能修改。
readonly修饰变量的类型的时候,只允许修饰数组和元组。
let o: readonly object = { name: 'hello' }//错误
let arr: readonly number[] = [1, 2, 3]//可以
只读数组
提到readonly,就可以说一下ReadonlyArray。它可以声明只读数组,数组的元素均不能修改。
let arr: ReadonlyArray<number> = [1, 2, 3]
// arr[0] = 23//报错
// arr.push(56)//移除了方法,因此arr.push是不存在的
// arr.length = 15//不可修改
let arr2: number[] = []
// arr2 = arr//不能赋给普通数组
arr2 = arr as number[]//只能使用类型断言
声明类型时,ReadonlyArray<number>和readonly number[]是等价的。
绕开额外属性检查的方法
1、前面提到的赋值
2、类型断言
类型断言就是告诉TS,我就认为它是这种类型,所以TS就不会进行额外的检查了。
interface IPerson {
readonly id: number;
name: string;
age?: number;
gender: string;
}
// let p: IPerson = { id: 1, name: 'Tom', gender: 'M', city: 'Shanghai' }//报错
let p: IPerson = { id: 1, name: 'Tom', gender: 'M', city: 'Shanghai' } as IPerson;//可以
3、索引签名
索引签名
索引签名是任意属性,包含string和number两种类型。
一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集,因为确定属性与可选属性也算任意属性中的一种。
interface IF {
name: string;
// id: number;//报错,因为number不是string的子类型
[prop: string]: string;
}
let m: IF = { name: 'Jack', h: 'hello' }
同时使用两种类型的任意属性时,数字索引的返回值必须是字符串索引返回值类型的子类型。
interface IF {
name: string;
// id: number;//报错,因为number不是string的子类型
[prop1: string]: string;
// [prop2: number]: number//报错,因为返回值number不是string的子类型
[prop2: number]: string
}
let m: IF = { name: 'Jack', h: 'hello', 1: 'hello' }
接口的继承
interface IPerson {
name: string
}
interface IStudent extends IPerson {
id: number
}
let st: IStudent = { name: 'Jack', id: 1 }
接口允许多继承。
interface IPerson {
name: string
}
interface IAnimal {
class: string
}
interface IStudent extends IPerson, IAnimal {
id: number
}
let st: IStudent = { name: 'Jack', id: 1, class: 'person' }
在TS中,若多继承的两个或多个父接口有相同属性,但定义的类型不同,则会报错。
这样不报错:
interface IPerson {
name: string;
city: string
}
interface IAnimal {
class: string;
city: string
}
interface IStudent extends IPerson, IAnimal {
id: number
}
let st: IStudent = { name: 'Jack', id: 1, class: 'person', city: 'Shanghai' }
这样报错:
interface IPerson {
name: string;
city: string
}
interface IAnimal {
class: string;
city: number
}
interface IStudent extends IPerson, IAnimal {//报错
id: number
}
let st: IStudent = { name: 'Jack', id: 1, class: 'person', city: 'Shanghai' }
因此,需要确保多个父接口之间没有共同属性或共同属性的类型相同。