这是我参与「第四届青训营 」笔记创作活动的第9天
「前言」
书接上回,解决上一篇文章留下来的一个疑问,ts 到底是怎么样对 对象 类型的做出更细致的检查
「什么是接口」
接口除了约束对象的类型之外,还可以约束 js 中几乎所有的 复杂类型
「接口约束对象」
接口基础
情景:创建多个对象,要求只能有 id 和 name 属性
用接口的方式实现:
interface IPerson {
id: number,
name: string,
}
const p1: IPerson = { id: 1, name: '张三' }
const p2: IPerson = { id: 1, name: '张三', age: 14 } // 报错,age 属性不在接口定义范围之内
习惯用法:接口首字母大写,在很多时候首字母用大写的 I 更有语义化的表示接口名
用 interface 关键字创建接口,对象中每一项内容必须与接口中的属性名和 属性值类型要求一样
但是我们仍然可以使用 delete 删除某些属性
接口也可以不用定义成类型
在某些时候,我们只想要某些属性为一个固定的值,就可以这样做
interface IPerson {
id: number,
name: string,
age: 18
}
同理:
let num: 18 = 18;
这是一种很愚蠢的做法
使用类型断言的方式创建对象
在某些时候我们在初始化对象的时候,只创建了一个空对象,但是之后会在对象中添加很多属性和方法,这时候就可以使用类型断言
const obj = <IPerson>{};
obj.id = 1;
obj.name = '张三';
obj.age = 18;
在初始化的时候,我们告诉编译器我们要创建一个 IPerson 类型的对象,但是暂时不实现,等待后续填充,这时,也意味着我们骗过了编译器,虽然是一个 IPerson,我们可以做一点手脚
const obj = <IPerson>{};
obj.name = '张三';
obj.age = 18;
编译器不会报错,但是不可以增加接口没有的属性和方法
接口创建方法
interface IPerson {
id: number,
name: string,
age: 18,
say(str: string): void
}
const p: IPerson = {
id: 1,
name: '张三',
age: 18,
say(str) {
console.log(str);
}
}
还有一种方式: =>
interface IPerson{
say: (str:string) => void
}
可选属性
某些时候,实现对象中的属性、方法不一定都要与接口完全一致
interface IPerson {
id?: number,
name: string,
age: 18,
say(str: string): void
}
const p: IPerson = {
name: '张三',
age: 18,
say(str) {
console.log(str);
}
}
id 之后有一个 ? 表示设置可选属性,p 中没有 id 不会报错
只读属性
可以在属性名前用 readonly来指定只读属性,除了初始化的时候修改都会报错
interface IPerson {
readonly id: number,
name: string,
}
const p: IPerson = {
name: '张三',
}
p.id = 6; // error
使用类型推断创建的方式也不能修改
? 和 readonly 可以一起使用
创建不可变数组
const arr: ReadonlyArray<number> = [1, 2, 3]
arr[0] = 0; // error
arr.push(4); // error
arr.length = 100; // error
完全不可以修改数组,并且不能赋值给除了 ReadonlyArrray 自身的其他类型
const arr: ReadonlyArray<number> = [1, 2, 3]
let a: Array<number> = []
a = arr; // error
如果数组中的元素时复杂类型数据就可以修改其中内容,但是不能重新赋值,也就是 浅readonly
通过索引签名创建接口
在上述的例子中,我们实现 IPerson 接口只能设置接口中提供的内容,但没办法自定义接口之外的内容,这个时候可以使用索引签名
interface IPerson {
id: number,
name: string,
age: 18,
[prop: string]: any
}
const obj = <IPerson>{};
obj.id = 1;
obj.name = '张三';
obj.age = 18;
obj.say = () => { }
通过 [任意字段: string]: 属性值类型 设置自定义内容,如果这里的属性值类型不为 any,请确保接口和对象中的所有内容的类型与属性值类型完全相同,即使用类型断言的方式创建也要遵守
属性名的类型只能为 string | number,因为对象中的索引可以通过 string 和 number 类型的键访问对应的值,而且在这个过程中会将 number 转化为 string
注意
1.类型推断对对象也适用
const o = {
name: 'o',
};
o.age = 18; // error 这里会自动给 o 创建一个类型,而这个类型中没有定义 age
2.如果将对象名赋值给一个新的变量,根据类型推断,新的变量依然要遵守之前的接口
interface IPerson {
id: number,
name: string,
age: 18,
}
const obj = {} as IPerson;
obj.id = 1;
obj.name = '张三';
obj.age = 18;
const obj1 = obj;
obj.sex = 'male' // error
3.但是可以通过函数的方式绕开检查
interface IPerson {
id: number,
name: string,
age: number, // 如果这里是 18 的话,下面类型检查依然会报错
}
function createPerson(person: IPerson) { };
const p1 = {
id: 1,
name: '张三',
age: 18,
sex: 'male'
}
createPerson({
id: 1,
name: '张三',
age: 18,
sex: 'male' // 接口未实现 sex
}) // error
createPerson(p1);
4.使用类型断言的方式也可以绕开检查
createPerson({
id: 1,
name: '张三',
age: 18,
sex: 'male' // 接口未实现 sex
} as IPerson)
「接口约束函数」
这种方式只能约束 箭头函数 和 表达式声明的函数
interface IFn {
(n1: number, n2:number): number
}
const test: IFn = (num1: number, num2: number) => {
return Math.random();
}
通过在 ()中限制参数的类型,:之后约束返回值类型(这里也可以不填返回值)
interface IFn {
(n1: number, n2:number)
}
使用类型断言方式创建函数
const test = <IFn>function (num) {
return Math.random();
}
混合类型接口
在 js 中,函数也是对象,意味着函数也可以有对应的属性和方法
interface IFn {
(): void,
props: number,
methods: number,
}
const fn = <IFn>() => { };
fn.props = 1;
fn.methods = 1;
「接口约束数组」--- 索引签名
索引签名常用作于设置 可索引的类型数据 的 索引方式 的类型
使用 [任意字段: 索引类型]: 索引返回值 的形式
interface StringArray {
[index: number]: string;
}
let myArray: StringArray = ["Bob", "Fred"];
索引类型必须为 number 和 string,但是 number 索引的返回值必须是 string 索引返回值类型的子类型
一旦设置上索引签名,保证所有接口的返回值类型与索引签名的返回值相同,而索引签名的返回值设置 any,可以避免这个问题
interface NumberDictionary {
[index: string]: number;
length: number; // 可以,length是number类型
name: string // 错误,`name`的类型与索引类型返回值的类型不匹配
}
只读对象
索引签名 与 readonly 一起使用模拟 只读对象
interface ReadonlyObject {
readonly [prop: string | number]: any;
}
const o: ReadonlyObject = {}
o.age = 18; // error
声明数组对象
interface IPerson {
name: string,
age: number,
}
const obj: IPerson[] = [
{
name: '张三',
age: 18,
},
{
name: '李四',
age: 19,
}
]
「接口继承」
接口也可以使用 extends 关键词来实现接口的复用
interface GrandFather {}
interface Father {
job: string
}
interface Son extends Father, GrandFather { // 可以继承多个接口
study: string
}
const s: Son = {
job: 'student',
study: 'ts'
}
子接口允许有相同的字段和约束类型,但是不允许字段相同、返回值不同,这意味着子接口 不能 对父接口的内容 重写
「接口继承类」
当接口继承了一个类类型时,它会继承类的成员但不包括其实现。
class Father {
sayHello() {
console.log('hello');
}
}
interface Son extends Father {
sayHi(): void;
}
const s: Son = {
sayHello() { },
sayHi() { }
}