如何在 TypeScript 中使用命名空間
介紹

TypeScript 是 JavaScript 語言的擴(kuò)展,它使用 JavaScript 運(yùn)行時(shí)和編譯時(shí)類型檢查器。
TypeScript 提供了多種方法來表示代碼中的對象,其中一種是使用接口。 TypeScript 中的接口有兩種使用場景:您可以創(chuàng)建類必須遵循的約定,例如,這些類必須實(shí)現(xiàn)的成員,還可以在應(yīng)用程序中表示類型,就像普通的類型聲明一樣。
您可能會(huì)注意到接口和類型共享一組相似的功能。
事實(shí)上,一個(gè)幾乎總是可以替代另一個(gè)。
主要區(qū)別在于接口可能對同一個(gè)接口有多個(gè)聲明,TypeScript 將合并這些聲明,而類型只能聲明一次。您還可以使用類型來創(chuàng)建原始類型(例如字符串和布爾值)的別名,這是接口無法做到的。
TypeScript 中的接口是表示類型結(jié)構(gòu)的強(qiáng)大方法。它們允許您以類型安全的方式使用這些結(jié)構(gòu)并同時(shí)記錄它們,從而直接改善開發(fā)人員體驗(yàn)。
在今天的文章中,我們將在 TypeScript 中創(chuàng)建接口,學(xué)習(xí)如何使用它們,并了解普通類型和接口之間的區(qū)別。
我們將嘗試不同的代碼示例,可以在 TypeScript 環(huán)境或 TypeScript Playground(一個(gè)允許您直接在瀏覽器中編寫 TypeScript 的在線環(huán)境)中遵循這些示例。
準(zhǔn)備工作
要完成今天的示例,我們將需要做如下準(zhǔn)備工作:
- 一個(gè)環(huán)境。我們可以執(zhí)行 TypeScript 程序以跟隨示例。要在本地計(jì)算機(jī)上進(jìn)行設(shè)置,我們將需要準(zhǔn)備以下內(nèi)容。
- 為了運(yùn)行處理 TypeScript 相關(guān)包的開發(fā)環(huán)境,同時(shí)安裝了 Node 和 npm(或 yarn)。本文教程中使用 Node.js 版本 為14.3.0 和 npm 版本 6.14.5 進(jìn)行了測試。要在 macOS 或 Ubuntu 18.04 上安裝,請按照如何在 macOS 上安裝 Node.js 和創(chuàng)建本地開發(fā)環(huán)境或如何在 Ubuntu 18.04 上安裝 Node.js 的使用 PPA 安裝部分中的步驟進(jìn)行操作。如果您使用的是適用于 Linux 的 Windows 子系統(tǒng) (WSL),這也適用。
- 此外,我們需要在機(jī)器上安裝 TypeScript 編譯器 (tsc)。為此,請參閱官方 TypeScript 網(wǎng)站。
- 如果你不想在本地機(jī)器上創(chuàng)建 TypeScript 環(huán)境,你可以使用官方的 TypeScript Playground 來跟隨。
- 您將需要足夠的 JavaScript 知識(shí),尤其是 ES6+ 語法,例如解構(gòu)、rest 運(yùn)算符和導(dǎo)入/導(dǎo)出。如果您需要有關(guān)這些主題的更多信息,建議閱讀我們的如何用 JavaScript 編寫代碼系列。
- 本文教程將參考支持 TypeScript 并顯示內(nèi)聯(lián)錯(cuò)誤的文本編輯器的各個(gè)方面。這不是使用 TypeScript 所必需的,但確實(shí)可以更多地利用 TypeScript 功能。為了獲得這些好處,您可以使用像 Visual Studio Code 這樣的文本編輯器,它完全支持開箱即用的 TypeScript。你也可以在 TypeScript Playground 中嘗試這些好處。
本教程中顯示的所有示例都是使用 TypeScript 4.2.2 版創(chuàng)建的。
在 TypeScript 中創(chuàng)建命名空間
在本節(jié)中,我們將一起來學(xué)習(xí)在 TypeScript 中創(chuàng)建命名空間以說明一般語法。
要?jiǎng)?chuàng)建命名空間,我們將使用命名空間關(guān)鍵字,后跟命名空間的名稱,然后是 {} 塊。
例如,我們將創(chuàng)建一個(gè) DatabaseEntity 命名空間來保存數(shù)據(jù)庫實(shí)體,就像我們使用對象關(guān)系映射 (ORM) 庫一樣。
將以下代碼添加到新的 TypeScript 文件中:
這聲明了 DatabaseEntity 命名空間,但尚未向該命名空間添加代碼。 接下來,在命名空間中添加一個(gè) User 類來表示數(shù)據(jù)庫中的一個(gè) User 實(shí)體:
我們可以在命名空間中正常使用 User 類。 為了說明這一點(diǎn),創(chuàng)建一個(gè)新的 User 實(shí)例并將其存儲(chǔ)在 newUser 變量中:
這是有效的代碼。
但是,如果我們嘗試在命名空間之外使用 User,TypeScript 編譯器會(huì)給我們返回錯(cuò)誤 2339:
如果我們想在命名空間之外使用類,則必須首先導(dǎo)出 User 類以在外部可用,如下面突出顯示的代碼所示:
我們現(xiàn)在可以使用完全限定名稱訪問 DatabaseEntity 命名空間之外的 User 類。 在這種情況下,完全限定名稱是 DatabaseEntity.User:
我們可以從命名空間中導(dǎo)出任何內(nèi)容,包括變量,然后這些變量將成為命名空間中的屬性。
在以下代碼中,我們將導(dǎo)出 newUser 變量:
由于變量 newUser 已導(dǎo)出,因此,我們可以將其作為命名空間的屬性進(jìn)行訪問。 運(yùn)行此代碼會(huì)將以下內(nèi)容打印到控制臺(tái):
就像接口一樣,TypeScript 中的命名空間也允許聲明合并。 這意味著同一命名空間的多個(gè)聲明將合并為一個(gè)聲明。 如果我們需要稍后在代碼中擴(kuò)展命名空間,這可以增加命名空間的靈活性。
使用前面的示例,這意味著如果我們再次聲明 DatabaseEntity 命名空間,我們將能夠使用更多屬性擴(kuò)展命名空間。 使用另一個(gè)命名空間聲明將一個(gè)新類 UserRole 添加到我們的 DatabaseEntity 命名空間:
在新的 DatabaseEntity 命名空間聲明中,我們可以使用以前在 DatabaseEntity 命名空間中導(dǎo)出的任何成員,包括從以前的聲明中導(dǎo)出的成員,而不必使用它們的完全限定名。
我們正在使用在第一個(gè)命名空間中聲明的名稱來將 UserRole 構(gòu)造函數(shù)中的用戶參數(shù)的類型設(shè)置為 User 類型,并在使用 newUser 值創(chuàng)建新的 UserRole 實(shí)例時(shí)。這僅是可能的,因?yàn)椋覀冊谥暗拿臻g聲明中導(dǎo)出了這些內(nèi)容。
現(xiàn)在,我們已經(jīng)了解了命名空間的基本語法,我們可以繼續(xù)研究 TypeScript 編譯器如何將命名空間轉(zhuǎn)換為 JavaScript。
檢查使用命名空間時(shí)生成的 JavaScript 代碼
TypeScript 中的命名空間不僅僅是一個(gè)編譯時(shí)特性。他們還更改了生成的 JavaScript 代碼。要了解有關(guān)命名空間如何工作的更多信息,我們可以分析支持此 TypeScript 功能的 JavaScript。
在這一步中,我們將獲取上一節(jié)中的代碼片段并檢查它們的底層 JavaScript 實(shí)現(xiàn)。
以我們在第一個(gè)示例中使用的代碼為例:
TypeScript 編譯器會(huì)為此 TypeScript 片段生成以下 JavaScript 代碼:
為了聲明 DatabaseEntity 命名空間,TypeScript 編譯器創(chuàng)建一個(gè)名為 DatabaseEntity 的未初始化變量,然后,創(chuàng)建一個(gè)立即調(diào)用函數(shù)表達(dá)式 (IIFE)。 此 IIFE 接收單個(gè)參數(shù) DatabaseEntity || (DatabaseEntity = {}),這是 DatabaseEntity 變量的當(dāng)前值。 如果未設(shè)置為真值,則將變量的值設(shè)置為空對象。
在將 DatabaseEntity 的值傳遞給 IIFE 時(shí)將其設(shè)置為空值是可行的,因?yàn)橘x值操作的返回值是被賦值的值。 在這種情況下,這是空對象。
在 IIFE 內(nèi)部,創(chuàng)建了 User 類,然后,將其分配給 DatabaseEntity 對象的 User 屬性。 newUser 屬性也是如此,我們將屬性分配給新 User 實(shí)例的值。
現(xiàn)在看一下第二個(gè)代碼示例,其中有多個(gè)命名空間聲明:
生成的 JavaScript 代碼如下所示:?
代碼的開頭看起來與之前的相同,未初始化的變量 DatabaseEntity,然后是一個(gè) IIFE,其中實(shí)際代碼設(shè)置了 DatabaseEntity 對象的屬性。這一次,雖然,還有另一個(gè) IIFE。這個(gè)新的 IIFE 與 DatabaseEntity 命名空間的第二個(gè)聲明相匹配。
現(xiàn)在,當(dāng)執(zhí)行第二個(gè) IIFE 時(shí),DatabaseEntity 已經(jīng)綁定到一個(gè)對象,因此,我們只是通過添加額外屬性來擴(kuò)展已經(jīng)可用的對象。
我們現(xiàn)在已經(jīng)了解了 TypeScript 命名空間的語法以及它們在底層 JavaScript 中的工作方式。有了這個(gè)上下文,我們現(xiàn)在可以運(yùn)行命名空間的一個(gè)常見用例:為外部庫定義類型而無需鍵入。
使用命名空間為外部庫提供類型
在這部分內(nèi)容中,我們將體驗(yàn)命名空間有用的場景之一:為外部庫創(chuàng)建模塊聲明。為此,我們將在 TypeScript 項(xiàng)目中編寫一個(gè)新文件來聲明類型,然后更改 tsconfig.json 文件以使 TypeScript 編譯器識(shí)別類型。
注意:要執(zhí)行后續(xù)步驟,需要一個(gè)可以訪問文件系統(tǒng)的 TypeScript 環(huán)境。如果您使用的是 TypeScript Playground,則可以通過單擊頂部菜單中的導(dǎo)出,然后在 CodeSandbox 中打開,將現(xiàn)有代碼導(dǎo)出到 CodeSandbox 項(xiàng)目。這將允許您創(chuàng)建新文件并編輯 tsconfig.json 文件。
并非 npm 注冊表中的每個(gè)可用包都捆綁了自己的 TypeScript 模塊聲明。這意味著在項(xiàng)目中安裝包時(shí),您可能會(huì)遇到與包缺少類型聲明相關(guān)的編譯錯(cuò)誤,或者必須使用所有類型都設(shè)置為 any 的庫。根據(jù)您使用 TypeScript 的嚴(yán)格程度,這可能是一個(gè)不希望的結(jié)果。
希望這個(gè)包將有一個(gè)由 DefinetelyTyped 社區(qū)創(chuàng)建的 @types 包,允許您安裝包并獲得該庫的工作類型。
但是,情況并非總是如此,有時(shí)您必須處理一個(gè)不捆綁其自己的類型模塊聲明的庫。在這種情況下,如果您想保持您的代碼完全類型安全,您必須自己創(chuàng)建模塊聲明。
例如,假設(shè)您正在使用一個(gè)名為 example-vector3 的向量庫,它使用單個(gè)方法 add 導(dǎo)出單個(gè)類 Vector3。此方法用于將兩個(gè) Vector3 向量相加。
庫中的代碼可能如下所示:
這導(dǎo)出了一個(gè)類,該類創(chuàng)建具有 x、y 和 z 屬性的向量,用于表示向量的坐標(biāo)分量。
接下來,看一下使用假設(shè)庫的示例代碼:
index.ts
example-vector3 庫沒有與它自己的類型聲明捆綁在一起,因此, TypeScript 編譯器將給出錯(cuò)誤 2307:
為了解決這個(gè)問題,我們現(xiàn)在將為這個(gè)包創(chuàng)建一個(gè)類型聲明文件。
首先,創(chuàng)建一個(gè)名為 types/example-vector3/index.d.ts 的新文件。
然后,在您喜歡的編輯器中打開它。
在此文件中寫入以下代碼:
在此代碼中,我們正在為 example-vector3 模塊創(chuàng)建類型聲明。 代碼的第一部分是聲明模塊塊本身。 TypeScript 編譯器將解析這個(gè)塊并解釋其中的所有內(nèi)容,就好像它是模塊本身的類型表示一樣。 這意味著我們在此處聲明的任何內(nèi)容,TypeScript 都將用于推斷模塊的類型。
現(xiàn)在,您說這個(gè)模塊導(dǎo)出了一個(gè)名為 vector3 的命名空間,該命名空間目前是空的。
保存并退出此文件。
TypeScript 編譯器當(dāng)前不知道您的聲明文件,因此您必須將其包含在您的 tsconfig.json 中。
為此,通過將 types 屬性添加到 compilerOptions 選項(xiàng)來編輯項(xiàng)目 tsconfig.json:
現(xiàn)在,如果我們返回原始代碼,我們將看到錯(cuò)誤已更改。TypeScript 編譯器現(xiàn)在給出錯(cuò)誤是2305:
當(dāng)我們?yōu)?example-vector3 創(chuàng)建模塊聲明時(shí),導(dǎo)出當(dāng)前設(shè)置為空命名空間。 沒有從該命名空間中導(dǎo)出 Vector3 類。
重新打開 types/example-vector3/index.d.ts 并編寫以下代碼:
在此代碼中,請注意,我們現(xiàn)在如何在 vector3 命名空間內(nèi)導(dǎo)出一個(gè)類。模塊聲明的主要目標(biāo)是提供由庫公開的值的類型信息。這樣,我們可以以類型安全的方式使用它。
在這種情況下,我們知道 example-vector3 庫提供了一個(gè)名為 Vector3 的類,該類在構(gòu)造函數(shù)中接受三個(gè)數(shù)字,并且具有用于將兩個(gè) Vector3 實(shí)例相加的 add 方法,并返回一個(gè)新實(shí)例作為結(jié)果。
我們無需在此處提供實(shí)現(xiàn),只需提供類型信息本身。不提供實(shí)現(xiàn)的聲明在 TypeScript 中稱為環(huán)境聲明,通常在 .d.ts 文件中創(chuàng)建這些聲明。
此代碼現(xiàn)在將正確編譯并具有 Vector3 類的正確類型。
使用命名空間,我們可以將庫導(dǎo)出的內(nèi)容隔離到單個(gè)類型單元中,在本例中為 vector3 命名空間。這使得自定義模塊聲明變得更加容易,甚至可以通過將類型聲明提交到 DefinetelyTyped 存儲(chǔ)庫來使所有開發(fā)人員都可以使用它。
最后結(jié)論
在今天的教程中,我們了解了 TypeScript 中命名空間的基本語法,并檢查了 TypeScript 編譯器將其更改為的 JavaScript。
我們還嘗試了命名空間的一個(gè)常見用例:為尚未鍵入的外部庫提供環(huán)境類型。
雖然,不推薦使用命名空間,但并不總是建議在代碼庫中使用命名空間作為代碼組織機(jī)制?,F(xiàn)代代碼應(yīng)該使用 ES 模塊語法,因?yàn)樗哂忻臻g提供的所有功能,并且從 ECMAScript 2015 開始,它成為規(guī)范的一部分。
但是,在創(chuàng)建模塊聲明時(shí),仍然建議使用命名空間,因?yàn)樗试S更簡潔的類型聲明。
如果你還想閱讀更多有關(guān) TypeScript 的教程文章,請看下面的推薦閱讀內(nèi)容,如果你覺得我今天的教程不錯(cuò),請點(diǎn)贊我,關(guān)注我,并將這篇文章分享給你的朋友,也許能夠幫助到他。
最后,感謝你的閱讀,編程快樂!




























