如何在 TypeScript 中使用接口

英文 | https://www.digitalocean.com/community/tutorials/how-to-use-interfaces-in-typescript
翻譯 | 楊小愛
一個環(huán)境。我們可以執(zhí)行 TypeScript 程序以跟隨示例。要在本地計算機上進行設(shè)置,我們將需要準備以下內(nèi)容。
為了運行處理 TypeScript 相關(guān)包的開發(fā)環(huán)境,同時安裝了 Node 和 npm(或 yarn)。本文教程中使用 Node.js 版本 為14.3.0 和 npm 版本 6.14.5 進行了測試。要在 macOS 或 Ubuntu 18.04 上安裝,請按照如何在 macOS 上安裝 Node.js 和創(chuàng)建本地開發(fā)環(huán)境或如何在 Ubuntu 18.04 上安裝 Node.js 的使用 PPA 安裝部分中的步驟進行操作。如果您使用的是適用于 Linux 的 Windows 子系統(tǒng) (WSL),這也適用。
此外,我們需要在機器上安裝 TypeScript 編譯器 (tsc)。為此,請參閱官方 TypeScript 網(wǎng)站。
如果你不想在本地機器上創(chuàng)建 TypeScript 環(huán)境,你可以使用官方的 TypeScript Playground 來跟隨。
您將需要足夠的 JavaScript 知識,尤其是 ES6+ 語法,例如解構(gòu)、rest 運算符和導入/導出。如果您需要有關(guān)這些主題的更多信息,建議閱讀我們的如何用 JavaScript 編寫代碼系列。
本文教程將參考支持 TypeScript 并顯示內(nèi)聯(lián)錯誤的文本編輯器的各個方面。這不是使用 TypeScript 所必需的,但確實可以更多地利用 TypeScript 功能。為了獲得這些好處,您可以使用像 Visual Studio Code 這樣的文本編輯器,它完全支持開箱即用的 TypeScript。你也可以在 TypeScript Playground 中嘗試這些好處。
interface Logger {log: (message: string) => void;}
與使用類型聲明創(chuàng)建普通類型類似,我們可以在 {} 中指定類型的字段及其類型:
interface Logger {log: (message: string) => void;}
Logger 接口表示一個對象,該對象具有一個名為 log 的屬性。 此屬性是一個接受字符串類型的單個參數(shù)并返回 void 的函數(shù)。
我們可以將 Logger 接口用作任何其他類型。 下面是一個創(chuàng)建與 Logger 接口匹配的對象字面量的示例:
interface Logger {log: (message: string) => void;}const logger: Logger = {log: (message) => console.log(message),};
使用 Logger 接口作為其類型的值必須具有與 Logger 接口聲明中指定的成員相同的成員。 如果某些成員是可選的,則可以省略它們。
由于值必須遵循接口中聲明的內(nèi)容,因此,添加無關(guān)字段將導致編譯錯誤。 例如,在對象字面量中,嘗試添加接口中缺少的新屬性:
interface Logger {log: (message: string) => void;}const logger: Logger = {log: (message) => console.log(message),otherProp: true,};
在這種情況下,TypeScript 編譯器會發(fā)出錯誤 2322,因為 Logger 接口聲明中不存在此屬性:
OutputType '{ log: (message: string) => void; otherProp: boolean; }' is not assignable to type 'Logger'.Object literal may only specify known properties, and 'otherProp' does not exist in type 'Logger'. (2322)
與使用普通類型聲明類似,可以通過附加 ? 將屬性轉(zhuǎn)換為可選屬性。 以他們的名義。
擴展其他類型
創(chuàng)建接口時,我們可以從不同的對象類型進行擴展,允許您的接口包含來自擴展類型的所有類型信息。 這使您能夠編寫具有一組通用字段的小型接口,并將它們用作構(gòu)建塊來創(chuàng)建新接口。
想象一下,我們?nèi)绻幸粋€ Clearable 接口,比如這個:
interface Clearable {clear: () => void;}
然后,我們可以創(chuàng)建一個從它擴展的新接口,繼承它的所有字段。 在以下示例中,接口 Logger 是從 Clearable 接口擴展而來的。 注意突出顯示的行:
interface Clearable {clear: () => void;}interface Loggerextends Clearable{log: (message: string) => void;}
Logger 接口現(xiàn)在還有一個 clear 成員,它是一個不接受參數(shù)并返回 void 的函數(shù)。 這個新成員繼承自 Clearable 接口。 就像我們這樣做一樣:
interface Logger {log: (message: string) => void;clear: () => void;}
當使用一組通用字段編寫大量接口時,我們可以將它們提取到不同的接口并更改接口以擴展創(chuàng)建的新接口。
回到前面使用的 Clearable 示例,假設(shè)我們的應用程序需要一個不同的接口,例如下面的 StringList 接口,來表示一個包含多個字符串的數(shù)據(jù)結(jié)構(gòu):
interface StringList {push: (value: string) => void;get: () => string[];}
通過使這個新的 StringList 接口擴展現(xiàn)有的 Clearable 接口,指定該接口還具有在 Clearable 接口中設(shè)置的成員,將 clear 屬性添加到 StringList 接口的類型定義中:
interface StringList extends Clearable {push: (value: string) => void;get: () => string[];}
接口可以從任何對象類型擴展,例如接口、普通類型,甚至是類。
帶有可調(diào)用簽名的接口
如果接口也是可調(diào)用的(也就是說,它也是一個函數(shù)),我們可以通過創(chuàng)建可調(diào)用簽名在接口聲明中傳達該信息。
通過在未綁定到任何成員的接口內(nèi)添加函數(shù)聲明并在設(shè)置函數(shù)的返回類型時使用 : 而不是 => 來創(chuàng)建可調(diào)用簽名。
例如,在 Logger 界面中添加一個可調(diào)用的簽名,如下面突出顯示的代碼所示:
interface Logger {(message: string): void;log: (message: string) => void;}
請注意,可調(diào)用簽名類似于匿名函數(shù)的類型聲明,但在返回類型中,我們使用的是 : 而不是 =>。 這意味著綁定到 Logger 接口類型的任何值都可以作為函數(shù)直接調(diào)用。
要創(chuàng)建與Logger 接口匹配的值,我們需要考慮接口的要求:
它必須是可調(diào)用的。
它必須有一個名為 log 的屬性,該屬性是一個接受單個字符串參數(shù)的函數(shù)。
讓我們創(chuàng)建一個名為 logger 的變量,它可以分配給 Logger 接口的類型:
interface Logger {(message: string): void;log: (message: string) => void;}const logger: Logger = (message: string) => {console.log(message);}logger.log = (message: string) => {console.log(message);}
要匹配 Logger 接口,該值必須是可調(diào)用的,這就是我們將 logger 變量分配給函數(shù)的原因:
interface Logger {(message: string): void;log: (message: string) => void;}const logger: Logger = (message: string) => {console.log(message);}logger.log = (message: string) => {console.log(message);}
然后,我們將 log 屬性添加到 logger 函數(shù):
interface Logger {(message: string): void;log: (message: string) => void;}const logger: Logger = (message: string) => {console.log(message);}logger.log = (message: string) => {console.log(message);}
這是 Logger 接口所要求的。 綁定到 Logger 接口的值還必須具有 log 屬性,該屬性是一個接受單個字符串參數(shù)并返回 void 的函數(shù)。
如果我們沒有包含 log 屬性,TypeScript Compiler 會給你錯誤 2741:
OutputProperty 'log' is missing in type '(message: string) => void' but required in type 'Logger'. (2741)
如果 logger 變量中的 log 屬性具有不兼容的類型簽名,TypeScript 編譯器將發(fā)出類似的錯誤,例如將其設(shè)置為 true:
interface Logger {(message: string): void;log: (message: string) => void;}const logger: Logger = (message: string) => {console.log(message);}logger.log = true;
在這種情況下,TypeScript 編譯器會顯示錯誤 2322:
OutputType 'boolean' is not assignable to type '(message: string) => void'. (2322)
將變量設(shè)置為具有特定類型的一個很好的功能,在這種情況下,將記錄器變量設(shè)置為具有記錄器接口的類型,TypeScript 現(xiàn)在可以推斷記錄器函數(shù)和日志中函數(shù)的參數(shù)類型 財產(chǎn)。
我們可以通過從兩個函數(shù)的參數(shù)中刪除類型信息來檢查。 請注意,在下面突出顯示的代碼中,消息參數(shù)沒有類型:
interface Logger {(message: string): void;log: (message: string) => void;}const logger: Logger = (message)=> {console.log(message);}logger.log = (message)=> {console.log(message);}
在這兩種情況下,編輯器應該仍然能夠顯示參數(shù)的類型是字符串,因為這是 Logger 接口所期望的類型。
帶有索引簽名的接口
可以向界面添加索引簽名,就像使用普通類型一樣,從而允許界面具有無限數(shù)量的屬性。
例如,如果想創(chuàng)建一個具有無限數(shù)量的字符串字段的 DataRecord 接口,可以使用以下突出顯示的索引簽名:
interface DataRecord {[key: string]: string;}
然后,我們可以使用 DataRecord 接口設(shè)置具有多個字符串類型參數(shù)的任何對象的類型:
interface DataRecord {[]: string;}const data: DataRecord = {fieldA: "valueA",fieldB: "valueB",fieldC: "valueC",// ...};
在本文中,我們使用 TypeScript 中可用的不同功能創(chuàng)建了接口,并學習了如何使用您創(chuàng)建的接口。
在接下來的內(nèi)容中,我們將了解更多關(guān)于類型和接口聲明之間的區(qū)別,并獲得聲明合并和模塊擴充的實踐。
類型和接口的區(qū)別
到目前為止,我們已經(jīng)看到接口聲明和類型聲明是相似的,具有幾乎相同的特性集。
例如,我們創(chuàng)建了一個從 Clearable 接口擴展而來的 Logger 接口:
interface Clearable {clear: () => void;}interface Logger extends Clearable {log: (message: string) => void;}
可以使用兩種類型聲明來復制相同的類型表示:
type Clearable = {clear: () => void;}type Logger = Clearable & {log: (message: string) => void;}
如前面內(nèi)容所示,接口聲明可用于表示各種對象,從函數(shù)到具有無限數(shù)量屬性的復雜對象。這也適用于類型聲明,甚至從其他類型擴展,因為,我們可以使用交集運算符 & 將多個類型相交。
由于類型聲明和接口聲明非常相似,因此,需要考慮各自獨有的特定功能,并在代碼庫中保持一致。選擇一種在代碼庫中創(chuàng)建類型表示,并且僅在需要僅對它可用的特定功能時才使用另一種。
例如,類型聲明具有接口聲明所缺乏的一些特性,例如:
聯(lián)合類型。
映射類型。
原始類型的別名。
僅可用于接口聲明的功能之一是聲明合并,我們將在接下來的內(nèi)容中學習它。重要的是要注意,如果您正在編寫一個庫并希望為庫用戶提供擴展庫提供的類型的能力,那么聲明合并可能很有用,因為類型聲明無法做到這一點。
聲明合并
TypeScript 可以將多個聲明合并為一個聲明,使他們能夠為同一個數(shù)據(jù)結(jié)構(gòu)編寫多個聲明,并在編譯期間將它們捆綁在一起,就像它們是一個單一類型一樣。
在文中,我們將看到它是如何工作的,以及為什么它在使用接口時很有幫助。
TypeScript 中的接口可以重新打開;也就是說,可以合并同一接口的多個聲明。當我們想要將新字段添加到現(xiàn)有界面時,這很有用。
例如,假設(shè)我們有一個名為 DatabaseOptions 的接口,如下所示:
interface DatabaseOptions {host: string;port: number;user: string;password: string;}
此接口將用于在連接到數(shù)據(jù)庫時傳遞選項。
稍后在代碼中,聲明一個具有相同名稱但具有一個名為 dsnUrl 的字符串字段的接口,如下所示:
interface DatabaseOptions {dsnUrl: string;}
當 TypeScript 編譯器開始讀取我們的代碼時,它會將 DatabaseOptions 接口的所有聲明合并為一個。 從 TypeScript 編譯器的角度來看,DatabaseOptions 現(xiàn)在是:
interface DatabaseOptions {host: string;port: number;user: string;password: string;dsnUrl: string;}
該接口包括我們最初聲明的所有字段,以及我們單獨聲明的新字段 dsnUrl。 兩個聲明已合并。
模塊擴充
當我們需要使用新屬性擴充現(xiàn)有模塊時,聲明合并很有幫助。 一個用例是,當我們向庫提供的數(shù)據(jù)結(jié)構(gòu)添加更多字段時。 這在名為 express 的 Node.js 庫中相對常見,它允許我們創(chuàng)建 HTTP 服務器。
使用 express 時,一個 Request 和一個 Response 對象被傳遞給我們的請求處理程序(負責為 HTTP 請求提供響應的函數(shù))。 Request 對象通常用于存儲特定于特定請求的數(shù)據(jù)。 例如,我們可以使用它來存儲發(fā)出初始 HTTP 請求的登錄用戶:
const myRoute = (req: Request, res: Response) => {res.json({ user: req.user});}
在這里,請求處理程序?qū)⒂脩糇侄卧O(shè)置為登錄用戶的 json 發(fā)送回客戶端。 使用負責用戶身份驗證的快速中間件,將登錄的用戶添加到代碼中另一個位置的請求對象。
Request 接口本身的類型定義沒有用戶字段,因此上面的代碼會給出類型錯誤 2339:
Property 'user' does not exist on type 'Request'. (2339)要解決這個問題,我們必須為 express 包創(chuàng)建一個模塊擴充,利用聲明合并向請求接口添加一個新屬性。
如果我們在 express 類型聲明中檢查 Request 對象的類型,我們會注意到它是一個添加在名為 Express 的全局命名空間中的接口,如 DefinitiveTyped 存儲庫中的文檔所示:
declare global {namespace Express {// These open interfaces may be extended in an application-specific manner via declaration merging.// See for example method-override.d.ts (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/method-override/index.d.ts)interface Request {}interface Response {}interface Application {}}}
注意:類型聲明文件是只包含類型信息的文件。 DefinitiveTyped 存儲庫是為沒有類型聲明的包提交類型聲明的官方存儲庫。 npm 上可用的 @types/<package> 包是從此存儲庫發(fā)布的。
要使用模塊擴充向 Request 接口添加新屬性,我們必須在本地類型聲明文件中復制相同的結(jié)構(gòu)。 例如,假設(shè)我們創(chuàng)建了一個名為 express.d.ts 的文件,如下所示,然后將其添加到 tsconfig.json 的 types 選項中:
import 'express';declare global {namespace Express {interface Request {user: {name: string;}}}}
從 TypeScript 編譯器的角度來看,Request 接口有一個用戶屬性,它們的類型設(shè)置為一個對象,該對象具有一個稱為字符串類型名稱的屬性。發(fā)生這種情況是因為同一接口的所有聲明都被合并了。
假設(shè)我們正在創(chuàng)建一個庫,并希望為我們的庫的用戶提供增加自己的庫提供的類型的選項,就像我們在上面使用 express 所做的那樣。在這種情況下,我們需要從庫中導出接口,因為普通類型聲明不支持模塊擴充。
結(jié)論
到這里,在本文提供的教程就結(jié)束了。
我們編寫了多個 TypeScript 接口來表示各種數(shù)據(jù)結(jié)構(gòu),發(fā)現(xiàn)了如何將不同的接口一起用作構(gòu)建塊來創(chuàng)建強大的類型,并了解了普通類型聲明和接口之間的區(qū)別。
我們現(xiàn)在可以開始為代碼庫中的數(shù)據(jù)結(jié)構(gòu)編寫接口,讓我們擁有類型安全的代碼和文檔。
推薦閱讀
如何在 TypeScript 中創(chuàng)建自定義類型
學習更多技能
請點擊下方公眾號
![]()

