Type 和 Interface 傻傻分不清楚?

如果你簡(jiǎn)歷上的技能有寫 TypeScript,那么面試官可能會(huì)問(wèn)你 type 和 interface 之間有什么區(qū)別?你知道怎么回答這個(gè)問(wèn)題么?如果不知道的話,那看完本文也許你就懂了。
類型別名 type 可以用來(lái)給一個(gè)類型起個(gè)新名字,當(dāng)命名基本類型或聯(lián)合類型等非對(duì)象類型時(shí)非常有用:
type MyNumber = number;
type StringOrNumber = string | number;
type Text = string | string[];
type Point = [number, number];
type Callback = (data: string) => void;
在 TypeScript 1.6 版本,類型別名開(kāi)始支持泛型。我們工作中常用的 Partial、Required、Pick、Record 和 Exclude 等工具類型都是以 type 方式來(lái)定義的。
// 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 只能用于定義對(duì)象類型,Vue 3 中的 App 對(duì)象就是使用 interface 來(lái)定義的:
// 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
}
由以上代碼可知,在定義接口時(shí),我們可以同時(shí)聲明對(duì)象類型上的屬性和方法。了解 type 和 interface 的作用之后,我們先來(lái)介紹一下它們的相似之處。
1、類型別名和接口都可以用來(lái)描述對(duì)象或函數(shù)。
類型別名
type Point = {
x: number;
y: number;
};
type SetPoint = (x: number, y: number) => void;
在以上代碼中,我們通過(guò) type 關(guān)鍵字為對(duì)象字面量類型和函數(shù)類型分別取了一個(gè)別名,從而方便在其他地方使用這些類型。
接口
interface Point {
x: number;
y: number;
}
interface SetPoint {
(x: number, y: number): void;
}
2、類型別名和接口都支持?jǐn)U展。
類型別名通過(guò) &(交叉運(yùn)算符)來(lái)擴(kuò)展,而接口通過(guò) extends 的方式來(lái)擴(kuò)展。
類型別名擴(kuò)展
type Animal = {
name: string
}
type Bear = Animal & {
honey: boolean
}
const bear: Bear = getBear()
bear.name
bear.honey
接口擴(kuò)展
interface Animal {
name: string
}
interface Bear extends Animal {
honey: boolean
}
此外,接口也可以通過(guò) extends 來(lái)擴(kuò)展類型別名定義的類型:
type Animal = {
name: string
}
interface Bear extends Animal {
honey: boolean
}
同樣,類型別名也可以通過(guò) &(交叉運(yùn)算符)來(lái)擴(kuò)展已定義的接口類型:
interface Animal {
name: string
}
type Bear = Animal & {
honey: boolean
}
了解完 type 和 interface 的相似之處之后,接下來(lái)我們來(lái)介紹它們之間的區(qū)別。
1、類型別名可以為基本類型、聯(lián)合類型或元組類型定義別名,而接口不行。
type MyNumber = number;
type StringOrNumber = string | number;
type Point = [number, number];
2、同名接口會(huì)自動(dòng)合并,而類型別名不會(huì)。
同名接口合并
interface User {
name: string;
}
interface User {
id: number;
}
let user: User = { id: 666, name: "阿寶哥" };
user.id; // 666
user.name; // "阿寶哥"
同名類型別名會(huì)沖突
type User = {
name: string;
};
// 標(biāo)識(shí)符“User”重復(fù)。ts(2300)
type User = { //Error
id: number;
};
利用同名接口自動(dòng)合并的特性,在開(kāi)發(fā)第三方庫(kù)的時(shí)候,我們就可以為使用者提供更好的安全保障。比如 webext-bridge 這個(gè)庫(kù),使用 interface 定義了 ProtocolMap 接口,從而讓使用者可自由地?cái)U(kuò)展 ProtocolMap 接口。
之后,在利用該庫(kù)內(nèi)部提供的 onMessage 函數(shù)監(jiān)聽(tīng)自定義消息時(shí),我們就可以推斷出不同消息對(duì)應(yīng)的消息體類型。
擴(kuò)展 ProtocolMap 接口
import { ProtocolWithReturn } from 'webext-bridge'
declare module 'webext-bridge' {
export interface ProtocolMap {
foo: { title: string }
bar: ProtocolWithReturn<CustomDataType, CustomReturnType>
}
}
監(jiān)聽(tīng)自定義消息
import { onMessage } from 'webext-bridge'
onMessage('foo', ({ data }) => {
// type of `data` will be `{ title: string }`
console.log(data.title)
}

使用類型別名的場(chǎng)景:
- 定義基本類型的別名時(shí),使用 type。
- 定義元組類型時(shí),使用 type。
- 定義函數(shù)類型時(shí),使用 type。
- 定義聯(lián)合類型時(shí),使用 type。
- 定義映射類型時(shí),使用 type。
使用接口的場(chǎng)景:
- 需要利用接口自動(dòng)合并特性的時(shí)候,使用 interface。
- 定義對(duì)象類型且無(wú)需使用 type 的時(shí)候,使用 interface。




























