TypeScript 中的類型與接口
在 TypeScript 中,定義類型有兩種方式:“類型”和“接口”。
人們經(jīng)常想知道該使用哪一種,答案并非適用于所有情況。有時(shí)一種更好,但在許多情況下,兩者可以互換使用。
我們來詳細(xì)了解一下類型和接口的不同點(diǎn)和相似點(diǎn)。
類型和類型別名
在 TypeScript 中,我們有一種叫做“類型”的東西,它幫助我們描述我們正在處理的數(shù)據(jù)類型。這就像為我們的信息提供一個(gè)藍(lán)圖。
基本類型包括字符串、布爾值、數(shù)字、數(shù)組、元組和枚舉。
但是,還有更多!我們還有“類型別名”??梢园阉鼈兿胂蟪深愋偷年欠Q。我們不是在創(chuàng)建新類型;我們只是給它們起了更友好的名稱。這使我們的代碼更容易閱讀和理解。
例如,我們可以為數(shù)字創(chuàng)建一個(gè)叫做“我的數(shù)字”的類型別名,所以我們可以不用寫“數(shù)字”,只需要說“我的數(shù)字”。
我們還可以為用戶數(shù)據(jù)創(chuàng)建一個(gè)類型別名,描述一個(gè)用戶的數(shù)據(jù)應(yīng)該是什么樣子。
當(dāng)人們討論“類型與接口”時(shí),他們實(shí)際上是在討論“類型別名與接口”。這就像給同一組事物起了不同的名稱。
TypeScript 中的接口
在 TypeScript 中,可以將接口視為一個(gè)對(duì)象必須遵循的規(guī)則或要求集合。這就像一份合約,說:“嘿,如果你想成為‘客戶’,你必須有‘名稱’和‘地址’?!?/p>
現(xiàn)在,還有另一種表達(dá)這些規(guī)則的方法。你可以使用所謂的“類型注解”。這有點(diǎn)像說,“這里是‘客戶’應(yīng)該長什么樣子”,然后列出‘名稱’和‘地址’屬性及其類型,就像你在接口中所做的那樣。
所以,無論你使用接口還是類型注解,你本質(zhì)上都在定義同樣的期望集合,對(duì)于‘客戶’應(yīng)該是什么樣子。這就像給同一組指令起了兩個(gè)不同的名字。
類型和接口的區(qū)別
類型和接口用于定義自定義數(shù)據(jù)結(jié)構(gòu)和形狀,但它們?cè)谛袨楹褪褂蒙嫌幸恍┎町悺?/p>
原始類型
使用類型:
type MyNumber = number;
在這種情況下,我們創(chuàng)建了一個(gè)類型別名 MyNumber,它是 number 原始類型的別名。
使用接口:
你不能使用接口直接定義像 number 這樣的原始類型。它們?cè)?TypeScript 中是預(yù)定義的。
聯(lián)合類型
使用類型:
type MyUnionType = number | string;
在這里,我們定義了一個(gè)類型 MyUnionType,它可以包含 number 或 string 的值。
使用接口:
接口通常不用于直接表示聯(lián)合類型。你應(yīng)該使用類型別名來表示這種用途。
函數(shù)類型
使用類型:
type MyFunctionType = (arg1: number, arg2: string) => boolean;
這定義了一個(gè)類型 MyFunctionType,用于一個(gè)函數(shù),該函數(shù)接受兩個(gè)參數(shù),一個(gè)數(shù)字和一個(gè)字符串,并返回一個(gè)布爾值。
使用接口:
interface MyFunctionInterface {
(arg1: number, arg2: string): boolean;
}
這個(gè)接口 MyFunctionInterface 表示相同的函數(shù)類型。
聲明合并
使用接口:
interface Person {
name: string;
}
interface Person {
age: number;
}
TypeScript 將自動(dòng)將這兩個(gè) Person 接口合并為一個(gè),包含 name 和 age 的屬性。
使用類型:
類型別名不支持聲明合并。如果你多次定義相同的類型別名,將導(dǎo)致錯(cuò)誤。
擴(kuò)展 vs. 交叉
使用擴(kuò)展:
interface A { propA: number; }
interface B extends A { propB: string; }
接口 B 擴(kuò)展了接口 A,繼承了 propA 屬性并添加了新的屬性 propB。
使用交叉:
type AB = A & { propB: string; }
在這里,我們使用交叉來組合 A 的屬性和新屬性 propB,以創(chuàng)建類型 AB。
擴(kuò)展時(shí)處理沖突
TypeScript 要求擴(kuò)展時(shí)具有相同名稱的屬性的類型匹配:
interface A { commonProp: number; }
interface B { commonProp: string; }
interface AB extends A, B { }
// 錯(cuò)誤: A 和 B 中的 'commonProp' 屬性必須具有相同的類型
typescript要解決沖突,你需要確保類型匹配或使用函數(shù)的方法重載。
處理元組類型
使用類型:
type MyTupleType = [number, string];
const tuple: MyTupleType = [42, "hello"];
在這里,我們使用 type 定義了一個(gè)元組類型,然后我們可以創(chuàng)建該元組類型的變量。
使用接口:
interface MyTupleInterface {
0: number;
1: string;
}
const tuple: MyTupleInterface = [42, "hello"];
你也可以使用接口定義元組類型,使用方式保持不變。
何時(shí)使用類型 vs. 接口
當(dāng)你需要組合或修改現(xiàn)有結(jié)構(gòu)時(shí),使用接口。如果你在處理庫或創(chuàng)建新的庫,接口是你的首選。
它們?cè)试S你合并或擴(kuò)展聲明,使得與現(xiàn)有代碼一起工作更加容易。當(dāng)你以面向?qū)ο缶幊痰姆绞剿伎紩r(shí),接口也更易讀。
當(dāng)你需要更強(qiáng)大的功能時(shí),選擇類型。TypeScript 的類型系統(tǒng)提供了諸如條件類型、泛型、類型保護(hù)等高級(jí)工具。
這些功能為你提供了更多控制你的類型的方式,幫助你創(chuàng)建健壯、強(qiáng)類型的應(yīng)用程序。接口無法提供這些能力。
你通常可以根據(jù)個(gè)人喜好使用類型或接口。然而,在以下情況下使用類型別名:
- ? 當(dāng)你想為基本數(shù)據(jù)類型(如‘字符串’或‘?dāng)?shù)字’)創(chuàng)建一個(gè)新名稱時(shí)。
- ? 當(dāng)定義更復(fù)雜的類型如聯(lián)合、元組或函數(shù)時(shí)。
- ? 當(dāng)重載函數(shù)時(shí)。
- ? 當(dāng)使用高級(jí)功能如映射類型、條件類型或類型保護(hù)時(shí)。
類型通常更靈活和表達(dá)性強(qiáng)。它們提供了一系列接口無法匹敵的高級(jí)功能,而 TypeScript 持續(xù)擴(kuò)展其能力。
我們使用類型別名自動(dòng)生成一個(gè)對(duì)象類型的 getter 方法,這是你無法通過接口做到的:
type Client = {
name: string;
address: string;
}
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type clientType = Getters<Client>;
// 結(jié)果是:
// {
// getName: () => string;
// getAddress: () => string;
// }
通過使用映射類型、模板文字和‘keyof’操作符,我們創(chuàng)建了一個(gè)類型,它可以為任何對(duì)象類型自動(dòng)生成 getter 方法。
此外,許多開發(fā)者更喜歡使用類型,因?yàn)樗鼈兣c函數(shù)式編程范式很契合。
TypeScript 中類型表達(dá)式的豐富性使得在保持類型安全的同時(shí),更容易與函數(shù)式概念如組合和不變性一起工作。