type 和 interface 傻傻分不清楚?

語言: CN / TW / HK

阿寶哥精心準備的 《輕鬆學 TypeScript》   影片教程已經更新到 第十二期 了, 通過形象生動的動畫,讓你輕鬆搞懂 TypeScript 的難點和核心知識點!

如果你簡歷上的技能有寫 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

掃碼檢視 輕鬆學 TypeScript 系列影片教程

(目前已更新  12  期)

閱讀完本文,相信你已經瞭解 type 和 interface 之間的區別了。你喜歡以這種形式學 TS 麼?喜歡的話,記得點贊與收藏喲。