Type 和 Interface 傻傻分不清楚?
如果你簡歷上的技能有寫 TypeScript,那么面試官可能會問你 type 和 interface 之間有什么區別?你知道怎么回答這個問題么?如果不知道的話,那看完本文也許你就懂了。
類型別名 type 可以用來給一個類型起個新名字,當命名基本類型或聯合類型等非對象類型時非常有用:
type MyNumber = number;
type StringOrNumber = string | number;
type Text = string | string[];
type Point = [number, number];
type Callback = (data: string) => void;
在 TypeScript 1.6 版本,類型別名開始支持泛型。我們工作中常用的 Partial、Required、Pick、Record 和 Exclude 等工具類型都是以 type 方式來定義的。
// lib.es5.d.ts
type Partial<T> = {
[P in keyof T]?: T[P];
};
type Required<T> = {
[P in keyof T]-?: T[P];
};
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
type Record<K extends keyof any, T> = {
[P in K]: T;
};
type Exclude<T, U> = T extends U ? never : T;
而接口 interface 只能用于定義對象類型,Vue 3 中的 App 對象就是使用 interface 來定義的:
// packages/runtime-core/src/apiCreateApp.ts
export interface App<HostElement = any> {
version: string
config: AppConfig
use(plugin: Plugin, ...options: any[]): this
mixin(mixin: ComponentOptions): this
component(name: string): Component | undefined // Getter
component(name: string, component: Component): this // Setter
directive(name: string): Directive | undefined
directive(name: string, directive: Directive): this
}
由以上代碼可知,在定義接口時,我們可以同時聲明對象類型上的屬性和方法。了解 type 和 interface 的作用之后,我們先來介紹一下它們的相似之處。
1、類型別名和接口都可以用來描述對象或函數。
類型別名
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
在以上代碼中,我們通過 type 關鍵字為對象字面量類型和函數類型分別取了一個別名,從而方便在其他地方使用這些類型。
接口
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
2、類型別名和接口都支持擴展。
類型別名通過 &(交叉運算符)來擴展,而接口通過 extends 的方式來擴展。
類型別名擴展
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
const bear: Bear = getBear()
bear.name
bear.honey
接口擴展
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
此外,接口也可以通過 extends 來擴展類型別名定義的類型:
type Animal = {
name: string
}
interface Bear extends Animal {
honey: boolean
}
同樣,類型別名也可以通過 &(交叉運算符)來擴展已定義的接口類型:
interface Animal {
name: string
}
type Bear = Animal & {
honey: boolean
}
了解完 type 和 interface 的相似之處之后,接下來我們來介紹它們之間的區別。
1、類型別名可以為基本類型、聯合類型或元組類型定義別名,而接口不行。
type MyNumber = number;
type StringOrNumber = string | number;
type Point = [number, number];
2、同名接口會自動合并,而類型別名不會。
同名接口合并
interface User {
name: string;
}
interface User {
id: number;
}
let user: User = { id: 666, name: "阿寶哥" };
user.id; // 666
user.name; // "阿寶哥"
同名類型別名會沖突
type User = {
name: string;
};
// 標識符“User”重復。ts(2300)
type User = { //Error
id: number;
};
利用同名接口自動合并的特性,在開發第三方庫的時候,我們就可以為使用者提供更好的安全保障。比如 webext-bridge 這個庫,使用 interface 定義了 ProtocolMap 接口,從而讓使用者可自由地擴展 ProtocolMap 接口。
之后,在利用該庫內部提供的 onMessage 函數監聽自定義消息時,我們就可以推斷出不同消息對應的消息體類型。
擴展 ProtocolMap 接口
import { ProtocolWithReturn } from 'webext-bridge'
declare module 'webext-bridge' {
export interface ProtocolMap {
foo: { title: string }
bar: ProtocolWithReturn<CustomDataType, CustomReturnType>
}
}
監聽自定義消息
import { onMessage } from 'webext-bridge'
onMessage('foo', ({ data }) => {
// type of `data` will be `{ title: string }`
console.log(data.title)
}
使用類型別名的場景:
- 定義基本類型的別名時,使用 type。
- 定義元組類型時,使用 type。
- 定義函數類型時,使用 type。
- 定義聯合類型時,使用 type。
- 定義映射類型時,使用 type。
使用接口的場景:
- 需要利用接口自動合并特性的時候,使用 interface。
- 定義對象類型且無需使用 type 的時候,使用 interface。