typescript(2)- 接口详解 | 青训营笔记

138 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的第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,因为对象中的索引可以通过 stringnumber 类型的键访问对应的值,而且在这个过程中会将 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"];

索引类型必须为 numberstring,但是 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() { }
}

「参考文章」

接口 · TypeScript中文网 · TypeScript——JavaScript的超集 (tslang.cn)