一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.
技巧13: 理解 type 和 interface 之间的差异
在ts中, 如果你想定义一个类型,你一般有两种做法:
type TState = {
name: string;
capital: string;
}
或者使用interface
interface IState {
name: string;
capital: string;
}
我们应该使用 type 还是 interface? 这些年两者的差别越来越小, 在许多情况两者都可以使用. 但是两者依然存在差异,所以你应该清楚在何种情况使用选用那个关键词.
首先是相似性:
-
如果你定义了一个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' };
-
type 和 interface 都可以定义索引签名:
type TDict = { [key: string]: string }; interface IDict { [key: string]: string; }
-
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中, 函数也是对象
-
type 和 interface 都可以使用泛型:
type TPair<T> = { first: T; second: T; } interface IPair<T> { first: T; second: T; }
-
type 可以 extend interface , 反过来也可以.
interface IStateWithPop extends TState { population: number; } type TStateWithPop = IState & { population: number; };
-
class 既可以用type来实现, 也可以用interface
class StateT implements TState { name: string = ''; capital: string = ''; } class StateI implements IState { name: string = ''; capital: string = ''; }
那 interface 和 type 差异体现在什么地方?
-
只有联合的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 除了可以用联合, 和可以利用映射或者条件类型.
-
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.
-
但是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.
总结:
- 当我们面对的类型很复杂, 你只能使用 type
- 当我们面对简单类型: 我们得考虑我们项目的连续性和扩展性:
-
连续性: 我们项目之前用interface, 那我们继续坚持使用interface, 之前用type 就坚持用type
-
扩展性: 如果声明合并对我们的项目有帮助, 我们就可以使用 interface
-