開啟TypeScript之旅的簡便方式
譯文【51CTO.com快譯】不可否認,TypeScript憑著智能感知、靜態分析(又名“類型檢查”)、以及內聯文檔等功能,已經在JavaScript社區中占據了一席之地。這些功能雖然并非TypeScript獨有,但是能夠在如下方面提高開發團隊的生產力,并改進的代碼質量:
- 通過實時的、自動完成的代碼建議,實現更快的代碼編寫。
- 發現并提示代碼中的拼寫錯誤。
- 方便新的成員熟悉代碼庫。
- 方便不同編程能力的團隊成員更好地協作。
- 可以防止破損的代碼被自動部署。
- 方便更加便捷、安全地修改和維護舊的代碼。
- 可用于項目的自動文檔化。
下面,我將分不同的階段,向您深入淺出地介紹如何開啟TypeScript之旅。
階段 1:在JavaScript文件中啟用TypeScript
作為一種最為普及的代碼編輯器,Visual Studio Code被廣泛地用來編寫JavaScript。不過,VS Code也內置了TypeScript,能夠提供上面提到的智能感知和自動建議等基本功能。例如,我們可以創建一個帶有屬性hello的對象world。當我們試著去訪問該對象的屬性時,VS Code會自動推薦hello。不僅如此,它還會告訴我們該屬性是一個字符串(string)類型。
這是一個非常基本但挺實用的類型檢查。而且,就算代碼庫中存在少許錯誤,這樣的類型檢查也能夠識別出來。例如,如果我們不小心將數字傳遞給了需要字符串類型的函數,那么就會被及時發現。因此,為了啟用針對JavaScript文件的全面類型檢查,您只需將注釋// @ts-check添加到待檢查的JavaScript文件的頂部便可。
據此,針對前面的例子,如果我們嘗試著用數字類型覆蓋對象的hello屬性,那么我們將會收到一條“Type ‘number’ is not assignable to type ‘string'”的警告。我們之前的函數之所以不會給出任何錯誤提示,是因為TypeScript并不知道輸入只能是字符串類型。為此,我們可以使用JSDoc向JavaScript添加各種類型。
此處的JSDoc是一個通過使用注釋,將上下文文檔添加到源代碼中的系統。它可以被用于自動生成文檔站點。TypeScript支持解析JSDoc的各項注釋。對于前面的示例函數,我們可以告訴TypeScript,yell函數的第一個參數是str(字符串)類型,及該函數是一個“字符串”。
現在,當我們向函數傳遞一個數字時,就會看到一個紅色的波浪形警告,而在將鼠標懸停其上方時,會出現“Argument of type ‘number’ is not assignable to parameter of type ‘string’.”的具體警告內容。您可以通過jsdoc.app,學習如何使用JSDoc記錄各項內容。
階段 2:在JavaScript項目中啟用TypeScript
如果您處理的是大型JavaScript項目,那么逐一添加// @ts-check到每個文件中,顯然過于繁瑣。幸運的是,VS Code提供了一些方法,來自動化此類工作。其中的一種方法是將“Check JS”配置設置為true。也就是說,我們在settings.json文件中設置為"javascript.implicitProjectConfig.checkJs": true。
如果您想在項目級別上為團隊中彼此協作的每個人都啟用此功能,則可以通過將tsconfig.json文件添加到項目的根目錄來實現。有關tsconfig.json中各種配置選項的詳細信息,請參閱--https://www.staging-typescript.org/tsconfig。
- JSON
- {
- "compilerOptions": {
- "checkJs": true, /* Report errors in .js files. */
- }
- }
JSDocs支持許多內置的類型,其中包括:string、number、boolean、array、promise、function等。不過,您可能需要創建某些超出基本定義的類型。例如,在定義一個“Dog”對象類型時,它需要具有“品種(breed)”、“年齡(age)”、以及可選的“名字(name)”屬性。那么,我們可以通過JSDoc來進行如下類型的定義:
- Plain Text
- /**
- * @typedef {object} Dog
- * @property {string} breed
- * @property {number} age
- * @property {string} [name]
- */
除了這種通過語法的方式來定義對象以外,您還可以通過參閱JSDoc文檔,獲悉更多TypeScript的泛型和實用類型。
下面,我們來看如何使用types.js文件,來定義各種全局類型,并向代碼庫中導入類型定義。據此,我們將Dog的類型定義放入該文件中,通過引用相對路徑,實現在各種不同文件中導入并使用該類型:
- JSON
- /** @type {import('./types).Dog} */
- const myDog = {
- breed: 'Chiweenie',
- age: 4,
- name: 'Nugget'
- }
如果需要讓Dog能夠在同一個文件中的多處使用到該類型,我們則可以在本地重新定義類型,以減少輸入:
- JSON
- /** @typedef {import('./types).Dog} Dog */
- /** @type {Dog} */
- const myDog = {
- breed: 'Chiweenie',
- age: 4,
- name: 'Nugget'
- }
您可能會發現,就目前而言,由于該文件不屬于JavaScript模塊,我們無法從types.js文件中導入任何內容。編輯器會提示:“File ‘/path/to/types.js’ is not a module.”。對此,您可以使用CommonJS或ES模塊語法,導出該文件。其導出值并不重要,它甚至可以undefined。例如,它可以是下面的任何一行:
- Plain Text
- // Works
- module.exports = {}
- // Sure
- exports.merp = ''
- // Why not?
- export default = null
- // Go for it
- export const thingamabob = undefined
當然,我們也可以是從第三方庫導入的類型定義。其語法雖然非常相似,但是并不會使用相對路徑,而是按照名稱去引用庫。例如,Vue.js(https://vuejs.org/)組件可以被輸入為:
- Plain Text
- /** @type {import('vue').Component} */
當然,并非所有庫都會提供類型定義。如果您的庫不提供類型定義,那么您可以去absolutetyped.org社區進行檢索。VS Code有一個名為“自動類型獲取(Automatic Type Acquisition)”的功能,會自動為你查找和安裝來自社區的類型定義。
如果您愿意,也可以遵從上述語法,在TypeScript文件中編寫類型定義,不過其文件擴展名為.ts。例如,如果想用TypeScript定義上述全局類型,我們可以將文件名更改為“type.ts”,其內容如下:
- TypeScript
- export interface Dog {
- breed: string
- age: number
- name?: string
- }
階段 3:將TypeScript集成到 CI/CD 管道中
下面,讓我們討論一些更為復雜的問題。如果在代碼中引入了錯誤,我們可以阻止代碼的部署嗎?在開始討論之前,我們假設:
- 您可以自如地使用命令行。
- 您對NPM已有一定的經驗;如果沒有,可以通過鏈接--https://docs.npmjs.com/getting-started,了解NPM的相關基礎知識。
- 您已熟悉CI/CD(持續集成/持續交付)的概念。
- 您已經擁有一個使用package.json文件初始化了的NPM項目。
我們的目標是在CI/CD環境中運行TypeScript編譯器,以便系統判定代碼是否存在類型錯誤。為此,我們需要為CI/CD環境提供一個TypeScript版本,以及一個待運行的腳本。
首先在終端里,我們需要在該項目的同一個文件夾中運行如下命令:
- npm install --save-devTypeScript
它會在本地安裝TypeScript,并把Typecript包作為開發依賴項,包含在package.json文件中予以更新。在了解了有哪些依賴項已被安裝后,TypeScript可以在不依賴VS Code的情況下服務于該項目。接著,我們可以使用如下命令,更新package.json文件的NPM腳本部分:
- Plain Text
- "ts": "tsc"
上述命令會添加一個名為ts的新腳本,并運行“tsc”命令(即typescript編譯器)。在運行“npm run ts”命令之前,我們需要解決兩個問題:
1. TypeScript需要知道待運行文件的路徑。
2. TypeScript只適用于.ts文件,而我們只有.js文件。
對此,您需要決定是繼續編寫JavaScript文件呢,還是去寫TypeScript文件?就我而言,我認為將所有內容保留在JavaScript中會更加簡單。畢竟TypeScript編譯器能夠很好地支持JavaScript文件,只不過未能在默認情況下啟用罷了。
為了明確地告知TypeScript去檢查哪些文件,我們需要使用allowJs配置,來允許它在JavaScript文件上運行。假設我們的JavaScript是寫在./src/index.js文件中的,那么我們將有如下選擇:
- 我們可以將“--allowJs ./src/index.js”添加到package.json文件中的NPM腳本中。
- 我們可以在每次調用NPM腳本:“npm run ts -- --allowJs ./src/index.js”時,添加上述命令。
- 我們可以在項目的根目錄中使用tsconfig.json文件。
由于我們已經擁有一個tsconfig.json文件,因此可以直接使用它。同時,我們需要定義files數組,并將allowJs和noEmit設置為true:
- JSON
- {
- "files": ["./src/index.js"],
- "compilerOptions": {
- "checkJs": true, /* Report errors in .js files. */
- "allowJs": true, /* Allow parsing javascript. */
- "noEmit": true, /* Do not emit outputs. */
- }
- }
由于TypeScript通常被用于轉譯代碼,因此我們可以在此將noEmit配置設置為true。這就意味著,它可以接受各種代碼,并以某種方式對其進行轉換。例如,它能夠接收一個TypeScript文件,然后返回一個JavaScript文件。
運行“npm run ts”命令,我們不會看到任何配置錯誤,而只是一些與代碼相關的錯誤。例如,在前面的示例中,如果我們試圖覆蓋被定義為字符串類型的屬性,就會產生錯誤。
至此,我們已經準備好了將這種類型檢查,集成到自動化部署的過程中。我們需要確保部署過程能夠順利地調用“npm run ts”命令。
值得一提的是,TypeScript雖然是一個很好的測試套件的補充,但它絕不是自動化測試的替代品。縱然TypeScript可以消除進入代碼庫時的各種類型錯誤,但是如果您的項目依賴于自動化部署的話,您還應該做好單元或集成測試。
TypeScript雖然可能會阻止您在應當運用數字的地方使用字符串,但是不會阻止您在只允許使用正數的情況下使用負數。因此,我建議您在系統中同時實施靜態分析和自動化測試。而我最喜歡的JavaScript項目測試工具是Jest。
階段 4:為開源庫生成類型定義
definitelytyped.org之類的社區項目可以為TypeScript提供類型定義等支持。我們可以采用當前的設置,而無需其他繁瑣的設置,讓TypeScript為我們的項目創建類型定義文件。在完成之后,我們可以發布自己的庫,以便用戶擁有豐富的類型定義,進而協助改善用戶與庫交互的體驗。
首先,我們需要對tsconfig.json文件進行更多的修改。其中包括:刪除“noEmit”設置(或將其設置為false),將“declaration”和“emitDeclarationOnly”設置為true,并為“outDir”提供路徑。下面展示了新的文件內容:
- {
- "files": ["./src/index.js"],
- "compilerOptions": {
- "checkJs": true, /* Report errors in .js files. */
- "allowJs": true, /* Allow parsing javascript. */
- "declaration": true, /* Generates '.d.ts' file. */
- "emitDeclarationOnly": true, /* Only generate '.d.ts'. No JS */
- "outDir": "./dist", /* Send output to this directory. */
- }
- }
您可以為“outDir”任選一個路徑,以便為生成的類型定義文件提供存放之處。由于我們已經使用了JavaScript,因此無需額外編譯步驟,便可將“emitDeclarationOnly”設置為true。在構建步驟中,您也可以使用Babel.js和Rollup.js。
在生成了類型定義文件,并發送至/dist文件夾后,我們需要修改package.json文件,以便告知NPM各種文件的存在性,并讓任何使用庫的開發人員受益。為了在NPM處發布內容,我們不但需要注意“name”和“version”屬性,還可以通過定義“types”(又名“typings”)屬性,來告知TypeScript在哪個文件夾中,查找庫的類型定義文件。當然,如果您的類型定義文件(以.d.ts結尾)與代碼同處一個文件夾中,則無需上述設置。下面展示了package.json文件的示范性內容:
- {
- "name": "nuggetisthebest",
- "version": "1.0.0",
- "types": "dist",
- "scripts": {
- "ts": "tsc"
- },
- "devDependencies": {
- "typescript": "^4.1.3"
- }
- }
請參見NPM文檔,以獲悉更多有關如何將庫發布到NPM處。
禁止在某些代碼上運行TypeScript
有時您在CI/CD管道中使用到了TypeScript,但并不希望它報告、甚至阻止項目的部署。對此,我們可以通過如下選項,來予以規避:
- 在某一行中禁用TypeScript:通過在任意行上添加注釋// @ts-ignore,您可以禁用TypeScript對于該行的分析。
- 在整個文件上禁用TypeScript:如果想禁用TypeScript對整個文件的檢查,您可以在文件的頂部添加注釋// @ts-nocheck。
- 在文件組或目錄組上禁用TypeScript:tsconfig.json文件有一個配置選項exclude,它允許您定義需要完全忽略的文件和目錄。
復雜類型
最終,您可能會需要重載函數(overloaded functions)之類非常復雜的類型定義。您可以在JSDoc中使用各種TypeScript功能,輕松地從TypeScript文件(.ts擴展名)中導入相關的類型。
- TypeScript
- /** @type { import('.types.ts').SomeType } */
- const someType = {}
這意味著在任何復雜的情況下,我們都可以使用常規的TypeScript文件,并將自定義類型導入到JSDoc中,而無需為整個項目編寫TypeScript。我們甚至都不需要依賴TypeScript編譯器。此外,我們還可以使用.d.ts文件,來聲明全局類型,具體請參見--https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html。
小結
總的說來,雖然使用.ts文件比較常見,但是我會基于如下原因,去使用JSDocs方法:
- 無需額外的構建步驟,只需普通的JavaScript。
- 可以將代碼復制并粘貼到任何JavaScript項目中。
- 并未增添新的語法,因此容易上手。
- 將更少的“噪音”引入代碼。
- 由于無需等待編譯器,因此開發的速度會更快。
下面是更多有關JSDoc的資源,可供您深入研究:
- 由VS Code提供的《Working with JavaScript》
- 由Devhints提供的《JSDoc Cheatsheet》
- 由Robert Biggs提供的《Type-Safe JavaScript with JSDoc》
- 由Robert Biggs提供的《JavaScript Type Linting》
- 由Stefan Baumgartner提供的《TypeScript without TypeScript – JSDoc superpowers》
原文標題:Get Started With TypeScript the Easy Way,作者:Austin Gil
【51CTO譯稿,合作站點轉載請注明原文譯者和出處為51CTO.com】