整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          TypeScript 項目實踐:四步走高效改造現有的 JavaScript 項目

          好,我是橙小梓,目前就職于 FreeWheel 核心業務團隊,主要負責前端開發工作。我想和你分享一下我在改造現有 JavaScript 項目上的實踐經驗,手把手帶你一起把現有的 JavaScript 項目 TypeScrip 化。

          改造背景

          先介紹一下改造背景。

          TypeScript 作為 JavaScript 的類型化超集,彌補了靜態、弱類型的 JavaScript 的缺陷,具有靜態類型聲明,可以減少不必要的類型判斷和人工查看類型的成本,開發過程中進行靜態類型檢查和類型提示,對提高開發效率有正向作用。基于 TypeScript 的優點和我們面臨的現狀,FreeWheel 核心業務前端開發團隊決定將前端開發語言從 JavaScript 向 TypeScript 切換。

          我們改造的項目業務非常復雜,參與開發的人員非常多(代碼行數 8 萬多行,前端開發人員 40 多人),以及在可預見的將來,項目會有大量的功能迭代。

          P.S.:在引入 TypeScript 的時候,我們使用的 TypeScript 版本是 4.2.2。

          改造過程

          接下來我會從以下 3 個方面來介紹我們是如何把 JavaScript 項目 TypeScrip 化的:

          1. 遷移方式探討
          2. 類型定義公約
          3. 代碼改造實操
          4. 遷移后的收獲

          遷移方式探討

          第一部分,我們來探討一下遷移方式。

          在決定把一個 JavaScript 項目 TypeScript 化的時候,首先我們需要去判斷我們這個項目的屬性以及我們本身具備的一些條件。

          如圖所示,第一個條件:判斷是否一個穩定的組件。這個意思就是在比較久的一段時間內并不會有一些功能迭代進來。在這樣的項目下,我們推薦你手動去維護一份.d.ts 的文件,可以做到只是對外提供類型,方便第三方的調用。如果不是一個穩定的組件,我們需要在將來不停地去維護它。

          如果有新的功能加進來的話,那我們就來看第二個條件:是否有足夠的時間窗口。如果有足夠的時間的話,我們推薦使用 TypeScript 來對原來的 JavaScript 代碼進行完整的重構。因為 TypeScript 的設計思維和 JavaScript 是不同的,它比較推薦接口設計先于代碼實現,所以在很大程度上,原來的 JavaScript 代碼是不能匹配這樣的設計而寫出來的。我們推薦用 TypeScript 重構,這樣會比較符合。

          如果我們沒有足夠的時間窗口該怎么辦?以我們的項目為例,我們選擇的方式是給模塊逐個添加類型來達到進行類型約束的目的。當然給模塊添加類型也是有順序的,推薦把底層的模塊先加類型,然后再是上層的模塊。

          類型定義公約

          接下來是我們在這個項目中組內約定的類型定義公約。約定這樣的類型定義公約目的有兩個:第一明確遷移進度,第二更好地做類型約束。

          我們約定了一些必須要遵守的公約:

          第一條,定義 type 盡量不使用 any。我們都知道,在代碼中大量的使用 any 其實可以明顯地降低移植難度,但是我們引入 TypeScript 的初衷是為了給代碼進行類型約束,那么使用了 any 之后就失去了這種類型約束的作用,所以我們這里把 eslink 中,這一條 no-explicit-any 設置成了 warn,我們并不會完全去禁止使用 any,但是不建議。

          其他三條,都是規定了在代碼中哪些位置是需要定義 type 的,分別是 Component 的 props、function/hook 的輸入輸出以及 state。

          看一下這個例子,useDate 是一個 hook,它里面包含了一個 state。Header 是一個 component,它有 props。左圖中,我們沒有很好地在這些位置定義清楚各自的類型。而右圖中,我們給 useDate 的返回值定義了類型,給 state 定義好了類型,同時給 Header 的 props 也定義好了類型。

          我們知道,當我們把一段代碼單獨提取出來成為一個 function,或者成為一個 Component,我們默認這樣的代碼是會被多次調用的。那么,在這些代碼被定義了類型之后,當我們去調用這樣的 function 或者 component,TypeScript 會給我們帶來怎么樣的幫助呢?

          首先,我們剛才定義了 useDate 的返回值,包含了三個值,它們的類型分別是 string,string 以及 function。那么我們在調用這個 hook 的時候,如果想要拿它的第四個返回值,這時編譯器就會告訴我們這個值已經超出了它的返回值的范圍,是有問題的,不可以這樣使用。

          我們剛才定義的 hook 第三個返回值其實是一個 function,它包含了兩個 string 類型的參數,它的返回值是 void。

          在我們調用這個 function 時,如果我們給它的第二個參數傳入的值是 number 類型,編譯器就會告訴我們這里的 number 類型并不被允許,我們只能傳入一個 string 類型的值。這就可以幫助我們提前發現這里傳入參數是否正確。

          然后是 Component,我們剛才定義了它的 props 的類型,它只包含了一個屬性 getData。我們在調用 Component 的時候,如果給它傳入別的參數,編譯器同樣會告訴我們這個屬性并不被 Component 所接收。

          完成了以上這幾處類型的定義,許多代碼中別的類型都可以通過類型推導來得到。這也就是用最小的工作量發揮 TypeScript 最大的好處的情況。所以我們約定了完成以上這幾處類型的定義,就算完成本階段的代碼遷移。

          接下來介紹一些我們推薦遵守的公約,這些公約主要是為了幫助我們能夠更好地實現類型約束。

          第一條,盡量避免類型重復定義。

          看這個例子,PowerUser 它的一些屬性其實是 User 和 Admin 的一個合集,同時它有特殊的 type。我們如果把左圖中的寫法,改成右圖中這樣,去掉 User 中 type 屬性,去掉 Admin 中 type 屬性,同時和它自定義的 type 組成它自己的類型,這樣其實更符合代碼邏輯。

          還有這樣的一種情況,上面的寫法其實是一種函數重載,這里的 diff() 支持輸入一個參數,支持輸入兩個參數,也支持輸入三個參數。我們推薦把這樣的情況寫成下面這樣的類型定義:第一個參數是 require 的,后面兩個參數都可以設置成 optional 的。

          這樣的例子在代碼中很常見,比如這里的 ignoreErrors,shouldAutoReload,isEditIO 這三個參數在 function 內部都是有默認值的。也就是說,我們在調用這種方法的時候并不是必須去傳入這樣的參數的。對于這種有默認值的屬性,在定義 type 時,我們推薦直接將它們設置成 optional。為什么要這樣做?首先它可以使我們的代碼更加簡潔清晰。其次,由于以上寫法更符合代碼邏輯,所以也就決定了它維護起來更加方便。

          第二條,推薦 使用 keyof 對 object 中的 key 進行約束。

          首先看上面這個代碼段,這個寫法會存在兩個比較大的問題:第一,我們無法確定返回值類型;第二,我們的參數把它定義為了 string,但是很可能會出現拼寫錯誤,而使這個 function 發生運行時的錯誤。

          我們推薦下面這種寫法,使用泛型 T 配合 keyof 來對這個 object 進行約束,同時也對它的 key 進行約束,就可以很好的解決這兩個問題。

          來看一下項目實例,這個函數它主要負責處理 URL 相關的一些功能。它從 URL 的 search 參數中得到一些鍵值對,并且把這些鍵值對轉化成一個 object 類型然后輸出。這里的 currentQuery 就是 object 類型。

          其次,它還提供了一個 deleteQueries 的 function。這個 function 支持輸入參數 keys,然后去除 search 中的一些鍵值對,并且把新得到的 search 反向 push 回 URL。

          我們在代碼中使用了泛型 T 對它的 object 進行約束,可以看到這里的第一個返回值就是泛型 T。

          然后我們使用 keyof T 對它的參數 filters 以及 useCallback 的參數 keys 進行了約束。filters 的作用是可以指定返回的 object 中包含哪些 key,而 keys 是指定了在 deleteQueries 中需要具體去刪除哪些 key。

          看一下實際的調用情況。在我們調用這個 hook 的時候傳入了一個指定類型 URLQueryForCampaignPage。這個類型包含了四個屬性,分別是 insertion_order_id、placement_id、pagelink_id 以及 step。當我們傳入這樣類型的時,就決定了我們得到的第一個返回值 Query 的類型被約束為只能包含這四個 key。那么當我們想去拿 Query 中的某一個屬性,比如要拿 test 屬性時,編譯器會直接告訴我們這個屬性并不包含在這個 object 中。

          定義它的第二個返回值,deleteQueries 這個 Callback,它的參數用 keyof T 約束了必須是這個 object 中的這些屬性所組成的 list。這時如果我們手動輸入某一個值,比如說 test 或一種拼寫錯誤,編譯器也會直接告訴我們這個 test 不在 object 中,從而提前發現了 bug。

          第三條,推薦 用 tuple 來代替 array 來約束數組長度。

          看這兩個代碼段,上面的寫法是用 array 來定義數組類型,下面的寫法是用 tuple 來定義數組類型。這兩種寫法都是可以的,但如果明確知道數組的長度,我們更推薦使用 tuple,為什么呢?

          看這個代碼實例。這個例子的 useMemo(),返回了兩個 number 類型的值組成的 list。如果我們用 array 來定義數組類型,要拿第三個返回值的時候,編譯器并不能檢查出錯誤,可能會導致運行時的 bug。只有我們把它定義成了下面這樣的情況,編譯器才能提前知道第三個屬性已經超出了返回值的范圍,提前發現錯誤。

          最后一條, id 盡量不要模糊定義它的類型(string | number),推薦把 id 明確定義為 string 或者 number 。這是我們在項目中實際踩過的坑,如果可以,我們希望能在項目初期就規避這樣的問題。

          比如這個 Message,我們的 id 可以明確成 string 話,千萬不要定義為 string | number 這樣的類型。為什么這么說?我們知道,后端返回給前端的數據可能來自不同的 service, 他們的 id 往往無法保證統一,有些接口的 id 是 number, 有些接口的 id 是 string。而 id 通常會作為唯一標識做匹配或者構建新的對象, 這就會給代碼造成額外的處理負擔,也增加了出錯率。

          比如這個實例,這里的 BFInfo 的 ioId 實際是 string 類型的,我們把它定義成了 number | string。這時 IOInfo 的 id 是 number 類型的。當我們要從 BFInfo 這個 list 中去匹配某一項 IOInfo 時,我們會寫如下語句。這段代碼在類型檢查的時候是會成功通過的,但事實上這里隱藏著一個 bug。

          可以看到,如果我們把剛才的 BFInfo 的 ioId 定義成了 string,那編譯器會直接告訴我們一個 number 類型的值。如果想跟一個 string 類型的值作比較,它的返回值永遠是 false,就說明這個語句是沒有意義的。只有我們把它們的類型明確了,我們才能提前發現這里有問題,才可以相應地去做代碼修改,比如把這兩個值統一轉成 number 類型或者 string 類型后再去作比較。

          代碼改造樣例

          講了上面兩部分之后,我們用具體實例來實際演示一下代碼改造的過程。

          00:00 / 00:00

          1.0x

          • 2.0x
          • 1.5x
          • 1.25x
          • 1.0x
          • 0.5x

          網頁全屏

          全屏

          00:00

          遷移后的收獲

          最后總結一下我們在把 JavaScript 項目 TypeScrip 化的過程中遇到的問題和獲得的收獲。

          第一個難點,在遷移階段,整個項目處于 JavaScript 和 TypeScrip 并存的狀態,項目的配置方案也因此變得比較復雜。

          左側是我們的 webpack 配置,我們針對 JavaScript 和 TypeScrip 引入了不同的加載器。而右側是 ESlint,我們針對 JavaScript 和 TypeScrip 又使用了不同的 rule 配置。

          第二個難點是我們原先的代碼設計很多時候并不利于類型化。

          看這個例子,由于原來的代碼設計,function 的某個參數 data 分別可以是三種完全不同的數據結構,而代碼中通過 if else 去對不同的數據進行處理。像這樣的代碼其實是非常不利于進行類型約束的。推薦的做法是把這樣的方法進行重構,但是由于我們在項目遷移的過程中仍然有大量的功能迭代進來,為了盡量地避免沖突以及把風險降到最低,我們選擇了這樣的折中的方式來進行約束。

          有遇到問題,但收獲也是非常大的。TypeScrip 化給我們帶來的第一份驚喜是讓我們發現了原先代碼中隱藏的 bug。

          第一個 bug 是我們在調用這個 function 給它傳參的時候,把這里的 key 和 value 直接給寫反了。

          第二個 bug 隱藏得比較深。component 有一個參數 value,value 值的屬性是 number 或者 string 的一個 list。而我們在 component 中定義了 state,這個 state 的類型是這里的 TreeOptionItem 所組成的 list。可以知道 string or number 和 TreeOptionItem 是兩個完全不同的類型,我們在寫代碼的時候,把 value 作為 state 的默認值這樣的寫法肯定是有問題的。這個在類型檢查的時編譯器直接告訴了我們。

          第三個例子也非常常見。我們在代碼中做一次 rename 之后可能忘記了去修改,這個文件中也引用到了這個參數,就導致這里的參數已經找不到了。像這樣的問題,tsc 也很容易就告訴了我們。

          總結

          最后,我們建議:對于多人參與開發的大中型項目,引入 TypeScript 將非常有利于后期的代碼維護。對于個人的小型項目,引入 TypeScript 的必要性并沒有那么強,但如果個人感興趣的話,推薦你依據個人喜好而定。比如你想要去體會 TypeScript 帶來的好處的話,小型項目也可以明顯感覺到。

          送給你一句話, 聽得再多東西都是別人的,親手試一試才能成為自己的。

          作者介紹

          橙小梓FreeWheel 核心業務團隊高級軟件工程師

          就職于 FreeWheel 核心業務團隊,主要負責前端開發工作,對前端前沿技術非常熱衷,致力于提升產品質量,優化用戶體驗。前豆瓣全棧開發工程師,對 ToB,ToC 的項目都有深刻的理解。

          來源: https://www.infoq.cn/article/PzkgsYi2OqY89o0KLjBW

          ebpack 是一種用于 JavaScript 和 TypeScript 項目的模塊打包工具。它可以將多個文件和依賴項轉換為單個或多個輸出文件,以提高應用程序的性能和可維護性。


          Webpack 的特點

          • 模塊化:Webpack 提供了模塊化功能,以管理應用程序中的依賴項。
          • 代碼分割:Webpack 可以將應用程序分割為多個入口點,以提高性能。
          • 緩存:Webpack 可以緩存已生成的Bundle,以提高構建時間。

          Webpack 的應用

          Webpack 用于各種項目,包括:

          • 單頁面應用程序 (SPA)
          • Web 應用程序
          • 移動應用程序
          • 電子商務網站

          Webpack 的優勢

          • 模塊化:Webpack 提供了模塊化功能,以管理應用程序中的依賴項。
          • 代碼分割:Webpack 可以將應用程序分割為多個入口點,以提高性能。
          • 緩存:Webpack 可以緩存已生成的Bundle,以提高構建時間。

          Webpack 的未來

          Webpack 繼續在 JavaScript 開發中保持其流行度。隨著其功能豐富、易于使用和廣泛的社區支持,我們預計 Webpack 將繼續成為 JavaScript 項目中的主力打包工具。

          結論

          Webpack 是一種強大的 JavaScript 打包工具,為開發人員提供了出色的功能,以打包和管理 JavaScript 項目。其模塊化、代碼分割和緩存功能使它成為各種項目中流行的選項。

          ebpack是一種模塊打包器,它可以將多個文件和模塊打包成一個或多個bundle,以便在瀏覽器中運行。從零入門到工程化實戰,你需要掌握以下內容:

          1. 了解Webpack的基本概念和原理,包括入口、出口、loader、plugin等。
          2. 安裝和配置Webpack,包括設置webpack.config.js文件和配置項。
          3. 掌握各種loader的使用,例如babel-loader、css-loader、file-loader等,以便將不同類型的文件轉換為JavaScript可識別的模塊。
          4. 掌握各種plugin的使用,例如uglifyjs-webpack-plugin、html-webpack-plugin等,以便進行代碼壓縮、混淆、優化和生成HTML文件等操作。
          5. 了解Webpack的代碼分割和懶加載功能,以及如何使用動態導入語法實現按需加載。
          6. 掌握Webpack的優化技巧,包括減少打包體積、提高打包速度和優化加載性能等。
          7. 了解Webpack在真實項目中的應用,例如在Vue、React、Angular等框架中的使用,以及如何與npm腳本、Gulp、Grunt等工具集成。
          8. 掌握Webpack的調試技巧,包括使用source map、分析打包結果和定位錯誤等。

          以上內容是Webpack入門到實戰所需掌握的基本知識點。通過學習這些內容,你可以更好地理解Webpack的工作原理和用法,并在實際項目中靈活應用。

          更多資源課程請關注猿學谷官網網站:https://www.yuanxuegu.com/


          主站蜘蛛池模板: 亚洲码一区二区三区| 中文字幕乱码一区二区免费| 波多野结衣中文一区| 日韩人妻无码一区二区三区久久99| 国产在线精品一区二区夜色| 奇米精品视频一区二区三区| 免费看无码自慰一区二区| 精品视频一区在线观看| 日本一区二区在线| 国产在线观看91精品一区| 亚洲图片一区二区| 色一情一乱一伦一区二区三区日本| 日本一区二区三区高清| 日韩精品午夜视频一区二区三区| 香蕉一区二区三区观| 色综合视频一区中文字幕| 亚洲一区二区三区无码中文字幕| 日本精品一区二区三本中文| 日本免费一区二区三区四区五六区| 亚洲国产精品一区二区九九 | 无码日韩精品一区二区三区免费 | 亚洲国产一区明星换脸| 本免费AV无码专区一区| 日本一区二区三区精品视频| av无码免费一区二区三区| 亚洲一区二区视频在线观看| 国产在线一区二区杨幂| 久久精品日韩一区国产二区 | 无码国产精品一区二区免费式影视| 亚洲欧洲一区二区三区| 午夜影视日本亚洲欧洲精品一区 | 国产高清不卡一区二区| 精品国产一区二区二三区在线观看 | 亚洲一区免费视频| 亚州日本乱码一区二区三区| 亚洲乱码一区二区三区在线观看| 一区二区视频免费观看| 亚洲AV美女一区二区三区| 久久亚洲中文字幕精品一区四| 无码一区二区三区亚洲人妻| 精品久久综合一区二区|