type 类型别名


类型别名用来给一个类型起个新名字。类型别名不仅可以用来表示基本类型,还可以用来表示对象类型、联合类型、元组和交集。

类型别名指的是为类型创建新名称。需要注意的是,我们并没有定义一个新类型。使用type关键字可能会让我们觉得是创建一个新类型,但我们只是给一个类型一个新名称。

所以我们所以 type 时,不是在创建新的类别,而是定义类型的一个别名而已。

  1. type userName = string; // 基本类型
  2. type userId = string | number; // 联合类型
  3. type arr = number[];
  4. // 对象类型
  5. type Person = {
  6. id: userId; // 可以使用定义类型
  7. name: userName;
  8. age: number;
  9. gender: string;
  10. isWebDev: boolean;
  11. };
  12. // 范型
  13. type Tree<T> = { value: T };
  14. const user: Person = {
  15. id: "901",
  16. name: "椿",
  17. age: 22,
  18. gender: "女",
  19. isWebDev: false,
  20. };
  21. const numbers: arr = [1, 8, 9];

比较 type 和 interface

当我们使用 TypeScript 时,就会用到 interface 和 type,平时感觉他们用法好像是一样的,没啥区别,都能很好的使用,所以也很少去真正的理解它们之间到底有啥区别。我们开发过经常或这么来定义类型:

  1. interface Point {
  2. x: number;
  3. y: number;
  4. }

或者这样定义:

  1. type Point = {
  2. x: number;
  3. y: number;
  4. };

interface 和 type之间的差异不仅仅是次要语法声明。那么,今天我们就来看看这两家伙之间存在啥不可告人的秘密。


interface 和 type 的相似之处

都可以用来描述对象或函数

两者都可以用来描述对象或函数,但语法不同:

  1. // type
  2. type Point = {
  3. x: number;
  4. y: number;
  5. };
  6. type SetPoint = (x: number, y: number) => void;
  7. // interface
  8. interface Point {
  9. x: number;
  10. y: number;
  11. }
  12. interface SetPoint {
  13. (x: number, y: number): void;
  14. }

两者都可以被继承

interface 和 type 都可以继承。另一个值得注意的是,接口和类型别名并不互斥。类型别名可以继承接口,反之亦然。

interface 继承 interface

  1. interface Person{
  2. name:string
  3. }
  4. interface Student extends Person { stuNo: number }

interface 继承 type

  1. type Person{
  2. name:string
  3. }
  4. interface Student extends Person { stuNo: number }

type 继承 type

  1. type Person{
  2. name:string
  3. }
  4. type Student = Person & { stuNo: number }

type 继承 interface

  1. interface Person{
  2. name:string
  3. }
  4. type Student = Person & { stuNo: number }

实现 implements

类可以实现 interface 以及 type(TS 2.7+)。但是,类不能实现联合类型。

  1. interface ICat{
  2. setName(name:string): void;
  3. }
  4. class Cat implements ICat{
  5. setName(name:string):void{
  6. // todo
  7. }
  8. }
  9. // type
  10. type ICat = {
  11. setName(name:string): void;
  12. }
  13. class Cat implements ICat{
  14. setName(name:string):void{
  15. // todo
  16. }
  17. }

类无法实现联合类型

  1. type Person = { name: string; } | { setName(name:string): void };
  2. // 无法对联合类型Person进行实现
  3. // error: A class can only implement an object type or intersection of object types with statically known members.
  4. class Student implements Person {
  5. name= "张三";
  6. setName(name:string):void{
  7. // todo
  8. }
  9. }

interface 和 type 的区别

可申声明数据类型不同

接口仅限于对象类型。类型别名声明可用于任何基元类型、联合或交集。在这方面,接口被限制为对象类型。

type 声明联合类型 交叉类型

虽然接口可以被扩展和合并,但它们不能以并集和交集的形式组合在一起。类型可以使用联合和交集操作符来形成新的类型。

  1. // object
  2. type PartialPointX = { x: number; };
  3. type PartialPointY = { y: number; };
  4. // 并集
  5. type PartialPoint = PartialPointX | PartialPointY;
  6. // 交集
  7. type PartialPoint = PartialPointX & PartialPointY;

interface 声明合并

如果你多次声明一个同名的接口,TypeScript 会将它们合并到一个声明中,并将它们视为一个接口。这称为声明合并 。但这不适用于类型。 如果我们尝试创建具有相同名称但不同的属性的两种类型,则 TypeScript 编译器将抛出错误。

  1. // These two declarations become:
  2. // interface Point { x: number; y: number; }
  3. interface Point { x: number; }
  4. interface Point { y: number; }
  5. const point: Point = { x: 1, y: 2 };

type 声明元组类型

元组(键值对)只能通过type关键字进行定义。

  1. type Data = [number, string];

不能使用接口声明元组。不过,我们可以在接口内部使用元组

  1. interface Point {
  2. coordinates: [number, number]
  3. }

索引签名问题

如果你经常使用TypeScript, 一定遇到过相似的错误:

Type ‘xxx’ is not assignable to type ‘yyy’ Index signature is missing in type ‘xxx’.

看个例子来理解问题:

  1. interface propType{
  2. [key: string] : string
  3. }
  4. let props: propType
  5. type dataType = {
  6. title: string
  7. }
  8. interface dataType1 {
  9. title: string
  10. }
  11. const data: dataType = {title: "订单页面"}
  12. const data1: dataType1 = {title: "订单页面"}
  13. props = data
  14. // Error:类型“dataType1”不可分配给类型“propType”; 类型“dataType1”中缺少索引签名
  15. props = data1

我们发现 dataType 和 dataType1 对应的类型一样,但是 interface 定义的就赋值失败,是什么原因呢?刚开始百思不解,最后我在 stack overflow上找到了一个相似的问题:
type 类型别名 - 图1
image.png
并且很幸运的找到了有效的答案:
type 类型别名 - 图2
image.png
翻译过来的大致意思就是:

Record<string,string>{[key:string]:string}相同。只有当该类型的所有属性都已知并且可以对照该索引签名进行检查时,才允许将子集分配给该索引签名类型。在您的例子中,从exampleTypeRecord<string,string>的所有内容都是可分配的。这只能针对对象字面量类型进行检查,因为一旦声明了对象字面量类型,就无法更改它们。因此,索引签名是已知的。 相反,在你使用interface去声明变量时,它们在那一刻类型并不是最终的类型。由于interface可以进行声明合并,所以总有可能将新成员添加到同一个interface定义的类型上

再结合声明合并的讲解, 这样就很好理解了。就是说interface定义的类型是不确定的, 比如后面再来一个:

  1. interface propType{
  2. title:number
  3. }

这样propType类型就被改变了。

我们应该使用哪一个?

官方推荐用 interface,其他无法满足需求的情况下用 type。
但其实,因为 联合类型 和 交叉类型 是很常用的,所以避免不了大量使用 type 的场景,一些复杂类型也需要通过组装后形成类型别名来使用。
所以,如果想保持代码统一,还是可选择使用 type。通过上面的对比,类型别名 其实可涵盖 interface 的大部分场景。
对于 React 组件中 props及 state,使用 type ,这样能够保证使用组件的地方不能随意在上面添加属性。如果有自定义需求,可通过 HOC二次封装。
编写三方库时使用interface,其更加灵活自动的类型合并可应对未知的复杂使用场景。