TS 体操 &(交叉类型) 和 接口的继承的区别

7/3/2022 TS

# TS 体操 &(交叉运算) 和 接口的继承的区别

交叉类型 和 接口继承都有合并 2 个对象的作用,可是实际使用起来还是有非常多需要注意的地方

# & 交叉类型

& 也称为 交叉类型(Intersection types),先通过 demo 了解一下

type Todo1 = {
  name: string
}

type Todo2 = {
  description: string
}

type Todo = Todo1 & Todo2
1
2
3
4
5
6
7
8
9

在以上的 demo 中

类型推导显示:type Todo = Todo1 & Todo2

实际效果:type Todo = { name:string; description: string }


情况 2

type NumberAndString = string & number // never

type Todo1 = {
  id: string
  title: string
  do(title: string, status: boolean): void
  fn(): void
}

type Todo2 = {
  id: number
  title: string | number
  do(title: string, id: number, status: boolean): void
  fn(): void
}

type Todo = Todo1 & Todo2

let todo: Todo = {
  id: 'string', // 报错  Type 'string' is not assignable to type 'never'.
  id: 1234, // 报错 Type 'number' is not assignable to type 'never'.
  title: 'title' // 只能合并为string类型
  do(title:string,status:boolean){},
  fn(){}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

NumberAndString 的类型推导结果是 never,因为不存在一个值既是 string 又是 number 的。

而 Todo 类型推导结果也一样,id 等于任何值都不对(因为是 never)。对于title 来说 string & string | number 的时候,其实还是 string

do 因为是函数类型,就算参数不一样也无法合并(使用的时候会报错),而 fn 因为函数相同,自然可以合并

# 得出个结论

  • & 交叉类型算的是 2 个类型的 并集。就是双方都有的东西才能合并到一起,不一样的将会被排除
  • 如果排除到最后都没有相同的东西,那么就会变 never 类型(即该类型不存在)
  • 内置类型(string,number,boolean,unio 联合类型)之间可以使用 &

# interface(接口) 的继承

接口的继承与 交叉类型最大的一个区别就是

交叉类型可以在 自定义 type、原始类型(string,number,boolean...) 、联合类型(string | number)、甚至是接口 中相互交叉

接口交叉也遵循 & 的规则,像下面的 demo,id 依旧是交叉为 never 类型

interface ITodo {
  id: string
  title: string
  do(title: string, status: boolean): void
  fn(): void
}

interface ITodo2 = {
  id: number
  title: string | number
  do(title: string, id: number, status: boolean): void
  fn(): void
}

type Todo = ITodo & ITodo2

var todo: Todo = {
  id: 1, // 报错
  title: '',
  do() {} // 报错
  fn(){}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

而继承只能从一个接口继承另外一个接口的内容

接口继承的时候,如果遇到同一键名但是值不符的,那么继承的时候就会报错,而不是合并为 never

包括方法的名称,哪怕继承过来后想重写,参数也必须和继承前保持一致

# 使用 type 还是使用 interface 的终极问题

type 和 interface 都是为了定义类型,约束变量。并且都支持拓展自己的字段

# 在继承/拓展字段的层面上

在日常使用中,type 可以用 & 运算符进行 2 个 type 的合并,合并过程中有 “冲突” 的字段会自动被合并为 never (在定义 type 的时候并没有感觉到这个字段会 never)

而 inteface 采用的则是 “继承”,在继承过程中,如果有冲突,那么在定义接口的时候就会立刻感知到

# 在定义类型的层面上

type 可以很方便的就组合基础类型,形成一个新的类型,比如

// 定义一个时间戳字段,时间戳可能是字符串,也可以是数字
type TimeStamp = string | number | bigInt

var time: TimeStamp = new Date().getTime()
1
2
3
4

而接口类型,则必须定义一个完整的接口。对于单个字段的限制,type 是略胜一筹


说到最后,估计也没有一个标准说明什么情况该用什么来定义类型。

如果开发的内容可能是一个 base 模块,很多地方都需要继承这个 base 进行开发,那推荐 interface。因为这很接口,而且当别人继承过去后新增/修改字段的时候会立刻知道,噢,原来这个字段 base 模块定义了,我这样改会冲突

而 type 类型也能定义,我个人感觉更多的是定义一些不需要对外的东西,或者不会经常被拿去继承后修改的东西

比如 TS 体操,看到的工具类基本都是 type 定义的,因为 type 既可以返回 {} 也能返回单个字段。开发工具类的时候最多也是 相互引用,没听说过 把 Pick 工具类继承过来加点功能 这样的话把。 type 用来定义工具类,定义单个字段,是非常合适的,定义对象也 OK(就是继承的时候没接口灵活)各有特点

所以到底选中 type / interface,还是看业务场景和代码编写的经验积累。当你写多了,一眼就能看出,这个用 xxx 是最合适的

# 最后

总结一下子

  • & 符用于合并 2 个/多个类型的 “交集”,当交集有冲突的时候则会自动转换为 never
  • & 对 type 和 interface 都能生效,对这 2 个定义都能合并
  • extend 只能从一个接口继承另外一个接口的内容
  • extend 接口继承的时候如果类型的 “交集” 有冲突,那么定义接口的时候就会有提示(报错),不会自动转换为 never

既然说到 type 和 interface,最后感觉基于上面类型合并的思考可以很顺利的引出什么场景适合用 type 还是用 interface

  • 定义单个字段的时候/定义工具类(做类型体操)的时候 type 非常合适

  • 定义 base 类/模块需要对外提供功能,提供接口/模块会被其他模块继承过去拓展的时候,interface 自然是当仁不让

Last Updated: 1/7/2024, 5:51:59 PM