<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          TypeScript 漸進(jìn)遷移指南

          共 7279字,需瀏覽 15分鐘

           ·

          2021-01-29 12:26


          英文 |?https://nextfe.com/

          英文作者 | Nathaniel?

          我之前寫了一篇《如何把 Node.js 項目從 JavaScript 遷移到 TypeScript 的指南》。指南的閱讀量超過了七千,不過其實當(dāng)時我對 JavaScript 和 TypeScript 的了解并不深入,把重心更多地放到特定工具上,而沒怎么從全局著手。最大的問題是我沒有提供遷移大型項目的解決方案。

          顯然,大型項目不可能在短時間內(nèi)重寫一切。因此,我很想分享下我最近學(xué)到的遷移項目到 TypeScript 的主要經(jīng)驗。

          遷移一個包含成千上百個文件的大型項目可能比你想象得要容易。整個過程主要分 3 步。

          注意:本文假定你已經(jīng)有一定的 TypeScript 基礎(chǔ),同時會使用 Visual Studio Code,否則,一些地方可能不一定直接適用。

          相關(guān)代碼:https://github.com/

          開始引入類型

          花了 10 個小時使用 console.log 排查問題后,你終于修復(fù)了 Cannot read property 'x' of undefined 問題,出現(xiàn)這個問題的原因是調(diào)用了可能為 undefined 的某個方法,給了你一個「驚喜」!你暗暗發(fā)誓,一定要把整個項目遷移到 TypeScript。但是看了看 lib、util、components 文件夾里上萬個 JavaScript 文件,你對自己說:「等以后吧,等我有空的時候。」當(dāng)然那一天永遠(yuǎn)也不會到來,因為總有各種酷炫的新特性等著加到應(yīng)用,客戶也不會因為項目是用 TypeScript 寫的就出大價錢。

          如果我告訴你,你可以增量遷移到 TypeScript 并立刻從中受益呢?

          添加神奇的 d.ts

          d.ts 是 TypeScript 的類型聲明文件,其中聲明了代碼中用到的對象和函數(shù)的各種類型,不包含任何具體的實現(xiàn)。

          假定你在寫一個即時通訊應(yīng)用,在 user.js 文件里有一個 user 變量和一些數(shù)組:

          const user = { id: 1234, firstname: 'Bruce', lastname: 'Wayne', status: 'online',};
          const users = [user];
          const onlineUsers = users.filter((u) => u.status === 'online');
          console.log( onlineUsers.map((ou) => `${ou.firstname} ${ou.lastname} is ${ou.status}`));

          那么對應(yīng)的 user.d.ts 會是:

          export interface User { id: number; firstname: string; lastname: string; status: 'online' | 'offline';}

          然后 message.js 里定義了一個函數(shù) sendMessage:

          function sendMessage(from, to, message)

          那么 message.d.ts 中相應(yīng)的類型會是:

          type sendMessage = (from: string, to: string, message: string) => boolean

          不過,sendMessage 也許沒那么簡單,參數(shù)的類型可能更復(fù)雜,也可能是一個異步函數(shù)。

          你可以使用 import 引入其他文件中定義的復(fù)雜類型,保持類型文件簡單明了,避免重復(fù)。

          import { User } from './models/user';type Message = { content: string; createAt: Date; likes: number;}interface MessageResult { ok: boolean; statusCode: number; json: () => Promise; text: () => Promise;}type sendMessage = (from: User, to: User, message: Message) => Promise

          注意:我這里同時使用了 type 和 interface,這是為了展示如何使用它們。你在項目中應(yīng)該主要使用其中一種。

          連接類型

          現(xiàn)在已經(jīng)有類型了,如何搭配 js 文件使用呢?

          大體上有兩種方式:

          Jsdoc typedef import

          假設(shè)同一文件夾下有 user.d.ts,可以在 user.js 文件中加入以下注釋:

          /** * @typedef {import('./user').User} User */
          /** * @type {User} */const user = { id: 1234, firstname: 'Bruce', lastname: 'Wayne', status: 'online',};
          /** * @type {User[]} */const users = [];
          // onlineUser 的類型會被自動推斷為 User[]const onlineUsers = users.filter((u) => u.status === 'online');
          console.log( onlineUsers.map((ou) => `${ou.firstname} ${ou.lastname} is ${ou.status}`));

          確保 d.ts 文件中有相應(yīng)的 import 和 export 語句,這一方式才能正確工作。否則,最終會得到 any 類型,顯然 any 類型不會是你想要的。

          三斜杠指令

          在無法使用 import 的場景下,三斜杠指令是導(dǎo)入類型的經(jīng)典方式。

          注意,你可能需要在 eslint 配置文件中加入以下內(nèi)容以免 eslint 把三斜杠指令視為錯誤:

          { "rules": { "spaced-comment": [ "error", "always", { "line": { "markers": ["/"] } } ] }}

          假設(shè) message.js 和 message.d.ts 在同一文件夾下,可以在 message.js 文件中加入以下三斜杠指令:

          /// (僅當(dāng)使用 user 類型時才加這一行)///

          然后給 sendMessage 函數(shù)加上以下注釋:

          /*** @type {sendMessage}*/function sendMessage(from, to, message)

          接著你會發(fā)現(xiàn) sendMessage 有了正確的類型,IDE 能自動補全 from、to、message 和函數(shù)的返回類型。

          或者你也可以這么寫:

          /*** @param {User} from* @param {User} to* @param {Message} message* @returns {MessageResult}*/function sendMessage(from, to, message)

          這是 jsDoc 書寫函數(shù)簽名的風(fēng)格,肯定沒有上一種寫法那么簡短。

          使用三斜杠指令時,應(yīng)該在 d.ts 文件中移除 import 和 export 語句,否則無法工作。如果你需要從其他文件中引入類型,可以這么寫:

          type sendMessage = ( from: import("./models/user").User, to: import("./models/user").User, message: Message) => Promise;

          這一差別背后的原因是 TypeScript 把不含 import 和 export 語句的 d.ts 文件視作環(huán)境(ambient)模塊聲明,包含 import 和 export 語句的則視為普通模塊文件,而不是全局聲明,所以無法用于三斜杠指令。

          注意,在實際項目中,選擇以上兩種方式中的一種,不要混用。

          自動生成 d.ts

          如果項目的 JavaScript 代碼中已經(jīng)有大量 jsDoc 注釋,那么你有福了,只需以下一行命令就能自動生成類型聲明文件:

          npx typescript src/**/*.js --declaration --allowJs --emitDeclarationOnly --outDir types

          以上命令中,所有 js 文件在 src 文件夾下,輸出的 d.ts 文件位于 types 文件夾下。

          babel 配置(可選)

          如果項目使用 babel,那么需要在 babelrc 里加上:

          { "exclude": ["**/*.d.ts"]}

          否則 *.d.ts 文件會被編譯為 *.d.js 文件,這毫無意義。

          現(xiàn)在你應(yīng)該就能享受到 TypeScript 的益處了(自動補全),無需額外配置 IDE,也不用修改 js 代碼的邏輯。

          類型檢查

          如果項目中 70% 以上的代碼都經(jīng)過以上步驟遷移后,你可以考慮開啟類型檢查,進(jìn)一步幫助檢測代碼中的小錯誤和問題。別擔(dān)心,你仍將繼續(xù)使用 JavaScript,也就是說不用改動構(gòu)建過程,也不用換庫。

          開啟類型檢查的主要步驟是在項目中加上 jsconfig.json。例如:

          { "compilerOptions": { "module": "commonjs", "target": "es5", "checkJs": true, "lib": ["es2015", "dom"] }, "baseUrl": ".", "include": ["src/**/*"], "exclude": ["node_modules"]}

          關(guān)鍵在于 checkJs 需要為真,這就為所有項目開啟了類型檢查。

          開啟后可能會碰到一大堆報錯,可以逐一修正。

          漸進(jìn)類型檢查

          // @ts-nocheck

          如果你希望以后再修復(fù)一些文件的類型問題,可以在文件頭部加上 // @ts-nocheck,TypeScript 編譯器會忽略這些文件。

          // @ts-ignore

          如果只想忽略某行而不是整個文件的話,可以使用 // @ts-ignore。加上這個注釋后,類型檢查會忽略下一行。

          使用這兩個標(biāo)記可以讓你慢慢修正類型檢查錯誤。

          第三方庫

          維護(hù)良好的庫

          如果用的是流行的庫,那 DefinitelyTyped 上多半已經(jīng)有類型定義了,只需運行以下命令:

          yarn add @types/your_lib_name --dev

          npm i @types/your_lib_name --save-dev

          注意:如果庫屬于某組織,庫名中包含 @ 和 /,那么在安裝相應(yīng)的類型定義文件時需要移除 @ 和 /,并在組織名后加上 __,例如 @babel/core 改為 babel__core。

          純 JS 庫

          如果用了一個作者 10 年前就已經(jīng)停止更新的 js 庫怎么辦?大多數(shù) npm 模塊仍然使用 JavaScript,沒有類型信息。添加 @ts-ignore 看起來不是一個好主意,因為你希望盡可能地確保類型安全。

          那你就需要通過創(chuàng)建 d.ts 文件增補模塊定義,建議創(chuàng)建一個 types 文件夾,加入自己的類型定義。然后就可以享受類型安全檢查了。

          declare module 'some-js-lib' { export const sendMessage: ( from: number, to: number, message: string ) => Promise;}

          完成這些步驟后,類型檢查應(yīng)該能很好地工作,可以避免代碼出現(xiàn)很多小錯誤。

          類型檢查升級

          修復(fù) 95% 以上類型檢查錯誤并確保每個庫都有相應(yīng)的類型定義后,你可以進(jìn)行最后一步:正式把整個項目的代碼遷移到 TypeScript。

          注意:我上一篇指南中提到的一些細(xì)節(jié)這里就不講了。

          把所有文件改為 .ts 文件

          現(xiàn)在是時候把 d.ts 文件和 js 文件合并了。由于幾乎所有的類型檢查錯誤都已修正,類型檢查已經(jīng)覆蓋所有模塊,基本上只需要把 require 改成 import 然后把代碼和類型定義都放到 ts 文件中。完成之前的工作后,這一步相當(dāng)簡單。

          把 jsconfig 改為 tsconfig

          現(xiàn)在我們需要的是 tsconfig.json 而不是 jsconfig.json。

          tsconfig.json 的例子:

          前端項目

          { "compilerOptions": { "target": "es2015", "allowJs": false, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "noImplicitThis": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "preserve", "lib": ["es2020", "dom"], "skipLibCheck": true, "typeRoots": ["node_modules/@types", "src/types"], "baseUrl": ".", }, "include": ["src"], "exclude": ["node_modules"]}

          后端項目

          { "compilerOptions": { "sourceMap": false, "esModuleInterop": true, "allowJs": false, "noImplicitAny": true, "skipLibCheck": true, "allowSyntheticDefaultImports": true, "preserveConstEnums": true, "strictNullChecks": true, "resolveJsonModule": true, "moduleResolution": "node", "lib": ["es2018"], "module": "commonjs", "target": "es2018", "baseUrl": ".", "paths": { "*": ["node_modules/*", "src/types/*"] }, "typeRoots": ["node_modules/@types", "src/types"], "outDir": "./built", }, "include": ["src/**/*"], "exclude": ["node_modules"]}

          因為這樣修改后類型檢查會變得更嚴(yán)格,所以可能需要修復(fù)一些額外的類型錯誤。

          修改 CI/CD 和構(gòu)建流程

          改到 TypeScript 后需要在構(gòu)建流程中生成可運行的代碼,通常在 package.json 中加上這一行就行:

          { "scripts":{ "build": "tsc" }}

          不過,前端項目通常用了 babel,你需要這樣設(shè)置項目:

          { "scripts": { "build": "rimraf dist && tsc --emitDeclarationOnly && babel src --out-dir dist --extensions .ts,.tsx && copyfiles package.json LICENSE.md README.md ./dist" }}

          別忘了改入口文件,比如:

          { "main": "dist/index.js", "module": "dist/index.js", "types": "dist/index.d.ts",}

          好了,萬事俱備。

          注意,dist 需要改成你實際使用的目錄。

          結(jié)語

          恭喜,代碼現(xiàn)在遷移到了 TypeScript,有嚴(yán)格的類型檢查保證。現(xiàn)在可以享受 TypeScript 帶來的所有好處,比如自動補全、靜態(tài)類型、esnext 語法、對大型項目友好。開發(fā)體驗大大提升,維護(hù)成本大大降低。編寫項目代碼不再是痛苦的過程,再也不會碰到 Cannot read property 'x' of undefined 報錯。

          替代方案:如果你希望一下子遷移整個項目到 TypeScript,可以參考 airbnb 團隊的指南。

          本文完?


          最后


          • 歡迎加我微信(winty230),拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...

          • 歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個專業(yè)的技術(shù)人...

          點個在看支持我吧
          瀏覽 47
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日皮太爽了我要看免费视频 | 男女日皮网站 | 久久久久久欧美二区电影网 | 99re4 | 丁香五月国内自拍 |