英文 | https://www.digitalocean.com/community/tutorials/how-to-use-namespaces-in-typescript
翻譯 | 楊小愛
一個環境。我們可以執行 TypeScript 程序以跟隨示例。要在本地計算機上進行設置,我們將需要準備以下內容。
為了運行處理 TypeScript 相關包的開發環境,同時安裝了 Node 和 npm(或 yarn)。本文教程中使用 Node.js 版本 為14.3.0 和npm 版本 6.14.5 進行了測試。要在 macOS 或 Ubuntu 18.04 上安裝,請按照如何在 macOS 上安裝 Node.js 和創建本地開發環境或如何在 Ubuntu 18.04 上安裝 Node.js 的使用 PPA 安裝部分中的步驟進行操作。如果您使用的是適用於 Linux 的 Windows 子系統 (WSL),這也適用。
此外,我們需要在機器上安裝 TypeScript 編譯器 (tsc)。為此,請參閱官方 TypeScript 網站。
如果你不想在本地機器上創建 TypeScript 環境,你可以使用官方的 TypeScript Playground 來跟隨。
您將需要足夠的 JavaScript 知識,尤其是 ES6+ 語法,例如解構、rest 運算符和導入/導出。如果您需要有關這些主題的更多信息,建議閱讀我們的如何用 JavaScript 編寫代碼系列。
本文教程將參考支持 TypeScript 並顯示內聯錯誤的文本編輯器的各個方面。這不是使用 TypeScript 所必需的,但確實可以更多地利用 TypeScript 功能。為了獲得這些好處,您可以使用像 Visual Studio Code 這樣的文本編輯器,它完全支持開箱即用的 TypeScript。你也可以在 TypeScript Playground 中嘗試這些好處。
在 TypeScript 中創建命名空間
這聲明了 DatabaseEntity 命名空間,但尚未向該命名空間添加代碼。 接下來,在命名空間中添加一個 User 類來表示數據庫中的一個 User 實體:
我們可以在命名空間中正常使用 User 類。 為了說明這一點,創建一個新的 User 實例並將其存儲在 newUser 變量中:
這是有效的代碼。
但是,如果我們嘗試在命名空間之外使用 User,TypeScript 編譯器會給我們返回錯誤 2339:
如果我們想在命名空間之外使用類,則必須首先導出 User 類以在外部可用,如下面突出顯示的代碼所示:
我們現在可以使用完全限定名稱訪問 DatabaseEntity 命名空間之外的 User 類。 在這種情況下,完全限定名稱是 DatabaseEntity.User:
我們可以從命名空間中導出任何內容,包括變量,然後這些變量將成為命名空間中的屬性。
在以下代碼中,我們將導出 newUser 變量:
由於變量 newUser 已導出,因此,我們可以將其作為命名空間的屬性進行訪問。 運行此代碼會將以下內容打印到控制台:
就像接口一樣,TypeScript 中的命名空間也允許聲明合併。 這意味着同一命名空間的多個聲明將合併為一個聲明。 如果我們需要稍後在代碼中擴展命名空間,這可以增加命名空間的靈活性。
使用前面的示例,這意味着如果我們再次聲明 DatabaseEntity 命名空間,我們將能夠使用更多屬性擴展命名空間。 使用另一個命名空間聲明將一個新類 UserRole 添加到我們的 DatabaseEntity 命名空間:
在新的 DatabaseEntity 命名空間聲明中,我們可以使用以前在 DatabaseEntity 命名空間中導出的任何成員,包括從以前的聲明中導出的成員,而不必使用它們的完全限定名。
我們正在使用在第一個命名空間中聲明的名稱來將 UserRole 構造函數中的用戶參數的類型設置為 User 類型,並在使用 newUser 值創建新的 UserRole 實例時。這僅是可能的,因為,我們在之前的命名空間聲明中導出了這些內容。
現在,我們已經了解了命名空間的基本語法,我們可以繼續研究 TypeScript 編譯器如何將命名空間轉換為 JavaScript。
檢查使用命名空間時生成的 JavaScript 代碼
TypeScript 中的命名空間不僅僅是一個編譯時特性。他們還更改了生成的 JavaScript 代碼。要了解有關命名空間如何工作的更多信息,我們可以分析支持此 TypeScript 功能的 JavaScript。
在這一步中,我們將獲取上一節中的代碼片段並檢查它們的底層 JavaScript 實現。
以我們在第一個示例中使用的代碼為例:
TypeScript 編譯器會為此 TypeScript 片段生成以下 JavaScript 代碼:
為了聲明 DatabaseEntity 命名空間,TypeScript 編譯器創建一個名為 DatabaseEntity 的未初始化變量,然後,創建一個立即調用函數表達式 (IIFE)。 此 IIFE 接收單個參數 DatabaseEntity || (DatabaseEntity = {}),這是 DatabaseEntity 變量的當前值。 如果未設置為真值,則將變量的值設置為空對象。
在將 DatabaseEntity 的值傳遞給 IIFE 時將其設置為空值是可行的,因為賦值操作的返回值是被賦值的值。 在這種情況下,這是空對象。
在 IIFE 內部,創建了 User 類,然後,將其分配給 DatabaseEntity 對象的 User 屬性。 newUser 屬性也是如此,我們將屬性分配給新 User 實例的值。
現在看一下第二個代碼示例,其中有多個命名空間聲明:
生成的 JavaScript 代碼如下所示:
代碼的開頭看起來與之前的相同,未初始化的變量 DatabaseEntity,然後是一個 IIFE,其中實際代碼設置了 DatabaseEntity 對象的屬性。這一次,雖然,還有另一個 IIFE。這個新的 IIFE 與 DatabaseEntity 命名空間的第二個聲明相匹配。
現在,當執行第二個 IIFE 時,DatabaseEntity 已經綁定到一個對象,因此,我們只是通過添加額外屬性來擴展已經可用的對象。
我們現在已經了解了 TypeScript 命名空間的語法以及它們在底層 JavaScript 中的工作方式。有了這個上下文,我們現在可以運行命名空間的一個常見用例:為外部庫定義類型而無需鍵入。
使用命名空間為外部庫提供類型
在這部分內容中,我們將體驗命名空間有用的場景之一:為外部庫創建模塊聲明。為此,我們將在 TypeScript 項目中編寫一個新文件來聲明類型,然後更改 tsconfig.json 文件以使 TypeScript 編譯器識別類型。
注意:要執行後續步驟,需要一個可以訪問文件系統的 TypeScript 環境。如果您使用的是 TypeScript Playground,則可以通過單擊頂部菜單中的導出,然後在 CodeSandbox 中打開,將現有代碼導出到 CodeSandbox 項目。這將允許您創建新文件並編輯 tsconfig.json 文件。
並非 npm 註冊表中的每個可用包都捆綁了自己的 TypeScript 模塊聲明。這意味着在項目中安裝包時,您可能會遇到與包缺少類型聲明相關的編譯錯誤,或者必須使用所有類型都設置為 any 的庫。根據您使用 TypeScript 的嚴格程度,這可能是一個不希望的結果。
希望這個包將有一個由 DefinetelyTyped 社區創建的 @types 包,允許您安裝包並獲得該庫的工作類型。
但是,情況並非總是如此,有時您必須處理一個不捆綁其自己的類型模塊聲明的庫。在這種情況下,如果您想保持您的代碼完全類型安全,您必須自己創建模塊聲明。
例如,假設您正在使用一個名為 example-vector3 的向量庫,它使用單個方法 add 導出單個類 Vector3。此方法用於將兩個 Vector3 向量相加。
庫中的代碼可能如下所示:
這導出了一個類,該類創建具有 x、y 和 z 屬性的向量,用於表示向量的坐標分量。
接下來,看一下使用假設庫的示例代碼:
index.ts
example-vector3 庫沒有與它自己的類型聲明捆綁在一起,因此, TypeScript 編譯器將給出錯誤 2307:
為了解決這個問題,我們現在將為這個包創建一個類型聲明文件。
首先,創建一個名為 types/example-vector3/index.d.ts 的新文件。
然後,在您喜歡的編輯器中打開它。
在此文件中寫入以下代碼:
在此代碼中,我們正在為 example-vector3 模塊創建類型聲明。 代碼的第一部分是聲明模塊塊本身。 TypeScript 編譯器將解析這個塊並解釋其中的所有內容,就好像它是模塊本身的類型表示一樣。 這意味着我們在此處聲明的任何內容,TypeScript 都將用於推斷模塊的類型。
現在,您說這個模塊導出了一個名為 vector3 的命名空間,該命名空間目前是空的。
保存並退出此文件。
TypeScript 編譯器當前不知道您的聲明文件,因此您必須將其包含在您的 tsconfig.json 中。
為此,通過將 types 屬性添加到 compilerOptions 選項來編輯項目 tsconfig.json:
現在,如果我們返回原始代碼,我們將看到錯誤已更改。TypeScript 編譯器現在給出錯誤是2305:
當我們為 example-vector3 創建模塊聲明時,導出當前設置為空命名空間。 沒有從該命名空間中導出 Vector3 類。
重新打開 types/example-vector3/index.d.ts 並編寫以下代碼:
在此代碼中,請注意,我們現在如何在 vector3 命名空間內導出一個類。模塊聲明的主要目標是提供由庫公開的值的類型信息。這樣,我們可以以類型安全的方式使用它。
在這種情況下,我們知道 example-vector3 庫提供了一個名為 Vector3 的類,該類在構造函數中接受三個數字,並且具有用於將兩個 Vector3 實例相加的 add 方法,並返回一個新實例作為結果。
我們無需在此處提供實現,只需提供類型信息本身。不提供實現的聲明在 TypeScript 中稱為環境聲明,通常在 .d.ts 文件中創建這些聲明。
此代碼現在將正確編譯並具有 Vector3 類的正確類型。
使用命名空間,我們可以將庫導出的內容隔離到單個類型單元中,在本例中為 vector3 命名空間。這使得自定義模塊聲明變得更加容易,甚至可以通過將類型聲明提交到 DefinetelyTyped 存儲庫來使所有開發人員都可以使用它。
最後結論
在今天的教程中,我們了解了 TypeScript 中命名空間的基本語法,並檢查了 TypeScript 編譯器將其更改為的 JavaScript。
我們還嘗試了命名空間的一個常見用例:為尚未鍵入的外部庫提供環境類型。
雖然,不推薦使用命名空間,但並不總是建議在代碼庫中使用命名空間作為代碼組織機制。現代代碼應該使用 ES 模塊語法,因為它具有命名空間提供的所有功能,並且從 ECMAScript 2015 開始,它成為規範的一部分。
但是,在創建模塊聲明時,仍然建議使用命名空間,因為它允許更簡潔的類型聲明。
如果你還想閱讀更多有關 TypeScript 的教程文章,請看下面的推薦閱讀內容,如果你覺得我今天的教程不錯,請點讚我,關注我,並將這篇文章分享給你的朋友,也許能夠幫助到他。
最後,感謝你的閱讀,編程快樂!
推薦閱讀
學習更多技能
請點擊下方公眾號