[译]<<Effective TypeScript>> 高效TypeScript62个技巧 技巧13

257 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.

技巧13: 理解 type 和 interface 之间的差异

在ts中, 如果你想定义一个类型,你一般有两种做法:

type TState = {
  name: string;
  capital: string;
}

或者使用interface

interface IState {
  name: string;
  capital: string;
}

我们应该使用 type 还是 interface? 这些年两者的差别越来越小, 在许多情况两者都可以使用. 但是两者依然存在差异,所以你应该清楚在何种情况使用选用那个关键词.

首先是相似性:

  1. 如果你定义了一个IState 或者 TState 值, 多加了一个population的属性, 你将会得到一模一样的报错:

    const wyoming: TState = {
      name: 'Wyoming',
      capital: 'Cheyenne',
      population: 500_000
    // ~~~~~~~~~~~~~~~~~~ Type ... is not assignable to type 'TState'
    //                    Object literal may only specify known properties, and
    //                    'population' does not exist in type 'TState'
    };
    
  2. type 和 interface 都可以定义索引签名:

    type TDict = { [key: string]: string };
    interface IDict {
      [key: string]: string;
    }
    
  3. type 和 interface 都可以定义函数类型:

    type TFn = (x: number) => string;
    interface IFn {
      (x: number): string;
    }
    
    const toStrT: TFn = x => '' + x;  // OK
    const toStrI: IFn = x => '' + x;  // OK
    

    type 的写法更自然, 如果该类型还有其他属性. 两种定义方法看起来更相似了:

    type TFnWithProperties = {
      (x: number): number;
      prop: string;
    }
    interface IFnWithProperties {
      (x: number): number;
      prop: string;
    }
    

    你可以记住这种写法. 因为在js中, 函数也是对象

  4. type 和 interface 都可以使用泛型:

    type TPair<T> = {
      first: T;
      second: T;
    }
    interface IPair<T> {
      first: T;
      second: T;
    }
    
  5. type 可以 extend interface , 反过来也可以.

    interface IStateWithPop extends TState {
      population: number;
    }
    type TStateWithPop = IState & { population: number; };
    
  6. class 既可以用type来实现, 也可以用interface

    class StateT implements TState {
      name: string = '';
      capital: string = '';
    }
    class StateI implements IState {
      name: string = '';
      capital: string = '';
    }
    

那 interface 和 type 差异体现在什么地方?

  1. 只有联合的types 没有联合的 interfaces

    type AorB = 'a' | 'b';
    

    扩展联合的types 非常有用, 如果你有分开的类型: Input 和 Output 类型. 和一个映射:

    type Input = { /* ... */ };
    type Output = { /* ...*/ };
    interface VariableMap {
      [name: string]: Input | Output;
    }
    

    可能需要一个有 name 属性的类型.:

    type NamedVariable = (Input | Output) & { name: string };
    

    上面就无法用interface表达. 一般来说,type 比 interface更强大. type 除了可以用联合, 和可以利用映射或者条件类型.

  2. type表达元祖和数组类型也更容易:

    type Pair = [number, number];
    type StringList = string[];
    type NamedNums = [string, ...number[]];
    

    当然也可以用interface表达 tuple (元祖)

    interface Tuple {
      0: number;
      1: number;
      length: 2;
    }
    const t: Tuple = [10, 20];  // OK
    

    但是很尴尬的是, 这会丢失所有tuple(元祖)的方法, 比如 concat.

  3. 但是interface 有一些性质type没有的. 其中一点就是interface可以进行扩展:

    interface IState {
      name: string;
      capital: string;
    }
    interface IState {
      population: number;
    }
    const wyoming: IState = {
      name: 'Wyoming',
      capital: 'Cheyenne',
      population: 500_000
    };  // OK
    

    这一性质被称作"声明合并". 第一次见到这的人可能会感到惊讶. 这主要用于类型声明文件. 主要为了用户方便填补类型声明的空白.

    ts利用性质用于合并 js不同版本导致的不同类型. 例如: Array interface, 定义在ib.es5.d.ts, 默认你都可以获取. 但是如果你将 ES2015 添加到 tsconfig.json 的lib 入口. ts就会将加载lib.es2015.d.ts , 然后声明合并, Array interface 就会多了很多方法比如: find.

    声明合并在正常也会起作用, 我们应该意识到这个的可能性, 必要的时候使用 type.

总结:

  1. 当我们面对的类型很复杂, 你只能使用 type
  2. 当我们面对简单类型: 我们得考虑我们项目的连续性和扩展性:
    • 连续性: 我们项目之前用interface, 那我们继续坚持使用interface, 之前用type 就坚持用type

    • 扩展性: 如果声明合并对我们的项目有帮助, 我们就可以使用 interface