国产精品电影_久久视频免费_欧美日韩国产激情_成年人视频免费在线播放_日本久久亚洲电影_久久都是精品_66av99_九色精品美女在线_蜜臀a∨国产成人精品_冲田杏梨av在线_欧美精品在线一区二区三区_麻豆mv在线看

我從Vuejs中學到了什么

開發 前端
本節內容需要大家對常用的模塊打包工具有一定的使用經驗,尤其是 rollup.js 以及 webpack。

 框架設計遠沒有大家想的那么簡單,并不是說只把功能開發完成,能用就算完事兒了,這里面還是有很多學問的。比如說,我們的框架應該給用戶提供哪些構建產物?產物的模塊格式如何?當用戶沒有以預期的方式使用框架時是否應該打印合適的警告信息從而提升更好的開發體驗,讓用戶快速定位問題?開發版本的構建和生產版本的構建有何區別?熱跟新(HMR:Hot Module Replacement)需要框架層面的支持才行,我們是否也應該考慮?再有就是當你的框架提供了多個功能,如果用戶只需要其中幾個功能,那么用戶是否可以選擇關閉其他功能從而減少資源的打包體積?所有以上這些問題我們都會在本節內容進行討論。

本節內容需要大家對常用的模塊打包工具有一定的使用經驗,尤其是 rollup.js 以及 webpack。如果你只用過或了解過其中一個也沒關系,因為它們很多概念其實是類似的。如果你沒有使用任何模塊打包工具那么需要你自行去了解一下,至少有了初步認識之后再來看本節內容會更好一些。

提升用戶的開發體驗

衡量一個框架是否足夠優秀的指標之一就是看它的開發體驗如何,我們拿 Vue3 舉個例子:

  1. createApp(App).mount('#not-exist') 

當我們創建一個 Vue 應用并試圖將其掛載到一個不存在的 DOM 節點時就會得到一個警告信息:

warn

從這條信息中我們得知掛載失敗了,并說明了失敗的原因:Vue 根據我們提供的選擇器無法找到相應的 DOM 元素(返回 null),正式因為這條信息的存在使得我們能夠清晰且快速的了解并定位問題,可以試想一下如果 Vue 內部不做任何處理,那么很可能得到的是一個 JS 層面的錯誤信息,例如:Uncaught TypeError: Cannot read property 'xxx' of null,但是根據此信息我們很難知道問題出在哪里。

所以在框架設計和開發的過程中,提供友好的警告信息是至關重要的,如果這一點做得不好那么很可能經常收到用戶的抱怨。始終提供友好的警告信息不僅能夠快速幫助用戶定位問題,節省用戶的時間,還能夠為框架收獲良好的口碑,讓用戶認為你是非常專業的。

在 Vue 的源碼中,你經常能夠看到 warn() 函數的調用,例如上面圖片中的信息就是由這句 warn() 函數調用打印的: 

  1. warn(  
  2.   `Failed to mount app: mount target selector "${container}" returned null.`  

對于 warn() 函數來說,由于它需要盡可能的提供有用的信息,因此它需要收集當前發生錯誤的組件的組件棧信息,所以如果你去看源碼你會發現有些復雜,但其實最終就是調用了 console.warn() 函數。

對于開發體驗來說,除了提供必要的警告信息,還有很多其他方面可以作為切入口,可以進一步提升用戶的開發體驗。例如在 Vue3 中當我們在控制臺打印一個 Ref 數據時: 

  1. const count = ref(0)  
  2. console.log(count) 

打開控制臺查看輸出,如下圖所示:

沒有任何處理的輸出

可以發現非常的不直觀,當然我們可以直接打印 count.value ,這樣就只會輸出 0,但是有沒有辦法在打印 count 的時候讓輸出的信息更有好呢?當然可以,瀏覽允許我們編寫自定義的 formatter,從而自定義輸出的形式。在 Vue 的源碼中你可以搜索到名為 initCustomFormatter 的函數,這個函數就是用來在開發環境下初始化自定義 formatter 的,以 chrome 為例我們可以打開 devtool 的設置,然后勾選 Console \-> Enable custom formatters:

然后刷新瀏覽器后查看控制臺,會發現輸出的內容變得非常直觀:

控制框架代碼的體積

框架的大小也是衡量框架的標準之一,在實現同樣功能的情況下當然是用越少的代碼越好,這樣體積就會越小,最后瀏覽器加載資源的時間也就越少。這時我們不禁會想,提供越完善的警告信息就意味著我們要編寫更多的代碼,這不是與控制代碼體積相駁嗎?沒錯,所以我們要想辦法解決這個問題。

如果我們去看 Vue 的源碼會發現,每一個 warn() 函數的調用都會配合 __DEV__ 常量的檢查,例如: 

  1. if (__DEV__ && !res) {  
  2.   warn(  
  3.     `Failed to mount app: mount target selector "${container}" returned null.`  
  4.   )  

可以看到,打印警告信息的前提是:__DEV__這個常量一定要為真,這里的 __DEV__ 常量就是達到目的的關鍵。

Vue 使用的是 rollup.js 對項目進行構建的,這里的 __DEV__ 常量實際上是通過 rollup 的配置來預定義的,其功能類似于 webpack 中的 DefinePlugin 插件。

Vue 在輸出資源的時候,會輸出兩個版本的資源,其中一個資源用于開發環境,如 vue.global.js ;另一個與其對應的用于生產環境,如:vue.global.prod.js ,通過文件名稱我們也能夠區分。

當 Vue 構建用于開發環境的資源時,會把 __DEV__ 常量設置為 true,這時上面那段輸出警告信息的代碼就等價于: 

  1. if (true && !res) {  
  2.   warn(  
  3.     `Failed to mount app: mount target selector "${container}" returned null.`  
  4.   )  

可以看到這里的 __DEV__ 被替換成了字面量 true ,所以這段代碼在開發環境是肯定存在的。

當 Vue 構建用于生產環境的資源時,會把 __DEV__ 常量設置為 false,這時上面那段輸出警告信息的代碼就等價于: 

  1. if (false && !res) {  
  2.   warn(  
  3.     `Failed to mount app: mount target selector "${container}" returned null.`  
  4.   )  

可以看到 __DEV__ 常量被替換為字面量 false ,這時我們發現這段分支代碼永遠都不會執行,因為判斷條件始終為假,這段永遠不會執行的代碼被稱為 Dead Code,它不會出現在最終的產物中,在構建資源的時候就會被移除,因此在 vue.global.prod.js 中是不會存在這段代碼的。

這樣我們就做到了在開發環境為用戶提供友好的警告信息的同時,還不會增加生產環境代碼的體積。

框架要做到良好的 Tree-Shaking

上文中我們提到通過構建工具設置預定義的常量 __DEV__,就能夠做到在生產環境使得框架不包含打印警告信息的代碼,從而使得框架自身的代碼量變少。但是從用戶的角度來看,這么做仍然不夠,還是拿 Vue 來舉個例子,我們知道 Vue 提供了內置的組件例如 <Transition> ,如果我們的項目中根本就沒有使用到該組件,那么 <Transition> 組件的代碼需要包含在我們項目最終的構建資源中嗎?答案是當然不需要,那如何做到這一點呢?這就不得不提到本節的主角 Tree-Shaking。

那什么是 Tree-Shaking 呢?在前端領域這個概念因 rollup 而普及,簡單的說所謂 Tree-Shaking 指的就是消除哪些永遠不會執行的代碼,也就是排除 dead-code,現在無論是 rollup 還是 webpack 都支持 Tree-Shaking。

想要實現 Tree-Shaking 必須滿足一個條件,即模塊必須是 ES Module,因為 Tree-Shaking 依賴 ESM 的靜態結構。我們使用 rollup 通過一個簡單的例子看看 Tree-Shaking 如何工作,我們 demo 的目錄結構如下: 

  1. ├── demo  
  2. │   └── package.json  
  3. │   └── input.js 
  4. │   └── utils.js

首先安裝 rollup:

yarn add rollup \-D # 或者 npm install rollup \-D

下面是 input.js 和 utils.js 文件的內容: 

  1. // input.js  
  2. import { foo } from './utils.js'  
  3. foo()  
  4. // utils.js  
  5. export function foo(obj) {  
  6.   obj && obj.foo  
  7.  
  8. export function bar(obj) {  
  9.   obj && obj.bar  

代碼很簡單,我們在 utils.js 文件中定義并導出了兩個函數,分別是 foo 和 bar,然后在 input.js 中導入了 foo 函數并執行,注意我們并沒有導入 bar 函數。

接著我們執行如下命令使用 rollup 構建:

  1. npx rollup input.js -f esm -o bundle.js 

這句命令的意思是以 input.js 文件問入口,輸出 ESM 模塊,輸出的文件名叫做 bundle.js 。命令執行成功后,我們打開 bundle.js 來查看一下它的內容: 

  1. // bundle.js  
  2. function foo(obj) {  
  3.   obj && obj.foo  
  4.  
  5. foo(); 

可以看到,其中并不包含 bar 函數,這說明 Tree-Shaking 起了作用,由于我們并沒有使用 bar 函數,因此它作為 dead-code 被刪除了。但是如果我們仔細觀察會發現,foo 函數的執行也沒啥意義呀,就是讀取了對象的值,所以它執行還是不執行也沒有本質的區別呀,所以即使把這段代碼刪了,也對我們的應用沒啥影響,那為什么 rollup 不把這段代碼也作為 dead-code 移除呢?

這就涉及到 Tree-Shaking 中的第二個關鍵點,即副作用。如果一個函數調用會產生副作用,那么就不能將其移除。什么是副作用?簡單地說副作用的意思是當調用函數的時候,會對外部產生影響,例如修改了全局變量。這時你可能會說,上面的代碼明顯是讀取對象的值怎么會產生副作用呢?其實是有可能的,想想一下如果 obj 對象是一個通過 Proxy 創建的代理對象那么當我們讀取對象屬性時就會觸發 Getter ,在 Getter 中是可能產生副作用的,例如我們在 Getter 中修改了某個全局變量。而到底會不會產生副作用,這個只有代碼真正運行的時候才能知道, JS 本身是動態語言,想要靜態的分析哪些代碼是 dead-code 是一件很有難度的事兒,上面只是舉了一個簡單的例子。

正因為靜態分析 JS 代碼很困難,所以諸如 rollup 等這類工具都會給我提供一個機制,讓我們有能力明確的告訴 rollup :”放心吧,這段代碼不會產生副作用,你可以放心移除它“,那具體怎么做呢?如下代碼所示,我們修改 input.js 文件: 

  1. import {foo} from './utils'  
  2. /*#__PURE__*/ foo() 

注意這段注釋代碼 /*#__PURE_*_/,該注釋的作用就是用來告訴 rollup 對于 foo() 函數的調用不會產生副作用,你可以放心的對其進行 Tree-Shaking,此時再次執行構建命令并查看 bundle.js 文件你會發現它的內容是空的,這說明 Tree-Shaking 生效了。

基于這個案例大家應該明白的是,在編寫框架的時候我們需要合理的使用/*#__PURE_*_/ 注釋,如果你去搜索 Vue 的源碼會發現它大量的使用了該注釋,例如下面這句: 

  1. export const isHTMLTag = /*#__PURE__*/ makeMap(HTML_TAGS) 

也許你會覺得這會不會對編寫代碼帶來很大的心智負擔?其實不會,這是因為通常產生副作用的代碼都是模塊內函數的頂級調用,什么是頂級調用呢?如下代碼所示: 

  1. foo() // 頂級調用  
  2. function bar() {  
  3.   foo() // 函數內調用  

可以看到對于頂級調用來說是可能產生副作用的,但對于函數內調用來說只要函數 bar 沒有被調用,那么 foo 函數的調用當然不會產生副作用。因此你會發現在 Vue 的源碼中,基本都是在一些頂級調用的函數上使用 /*#__PURE__*/ 注釋的。當然該注釋不僅僅作用與函數,它可以使用在任何語句上,這個注釋也不是只有 rollup 才能識別,webpack 以及壓縮工具如 terser 都能識別它。

框架應該輸出怎樣的構建產物

上文中我們提到 Vue 會為開發環境和生產環境輸出不同的包,例如 vue.global.js 用于開發環境,它包含了必要的警告信息,而 vue.global.prod.js 用于生產環境,不包含警告信息。實際上 Vue 的構建產物除了有環境上的區分之外,還會根據使用場景的不同而輸出其他形式的產物,這一節我們將討論這些產物的用途以及在構建階段如何輸出這些產物。

不同類型的產物一定是有對應的需求背景的,因此我們從需求講起。首先我們希望用戶可以直接在 html 頁面中使用 <script>標簽引入框架并使用: 

  1. <body>  
  2.   <script src="/path/to/vue.js"></script>  
  3.   <script>  
  4.   const { createApp } = Vue  
  5.   // ...  
  6.   </script>  
  7. </body> 

為了能夠實現這個需求,我們就需要輸出一種叫做 IIFE 格式的資源,IIFE 的全稱是 Immediately Invoked Function Expression ,即”立即調用的函數表達式“,可以很容易的用 JS 來表達: 

  1. (function () {  
  2.   // ...  
  3. }())  
  4. 如上代碼所示,這就是一個立即執行的函數表達式。實際上 vue.globale.js 文件就是 IIFE 形式的資源,大家可以看一下它的代碼結構:  
  5. var Vue = (function(exports){  
  6.   // ...  
  7.  exports.createApp = createApp;  
  8.   // ...  
  9.   return exports  
  10. }({})) 

這樣當我們使用 <script> 標簽直接引入 vue.global.js 文件后,那么全局變量 Vue 就是可用的了。

在 rollup 中我們可以通過配置 format: 'iife' 來實現輸出這種形式的資源: 

  1. // rollup.config.js  
  2. const config = {  
  3.   input: 'input.js',  
  4.   output: {  
  5.     file: 'output.js',  
  6.     format: 'iife' // 指定模塊形式  
  7.   }  
  8.  
  9. export default config 

不過隨著技術的發展和瀏覽器的支持,現在主流瀏覽器對原生 ESM 模塊的支持都不錯,所以用戶除了能夠使用 <script> 標簽引用 IIFE 格式的資源外,還可以直接引如 ESM 格式的資源,例如 Vue3 會輸出 vue.esm-browser.js 文件,用戶可以直接用 <script> 標簽引入: 

  1. <script type="module" src="/path/to/vue.esm-browser.js"></script> 

為了輸出 ESM 格式的資源就需要我們配置 rollup 的輸出格式為:format: 'esm'。

你可能已經注意到了,為什么 vue.esm-browser.js 文件中會有 -browser 字樣,其實對于 ESM 格式的資源來說,Vue 還會輸出一個 vue.esm-bundler.js 文件,其中 -browser 變成了 -bundler。為什么這么做呢?我們知道無論是 rollup 還是 webpack 在尋找資源時,如果 package.json 中存在 module 字段,那么會優先使用 module 字段指向的資源來代替 main 字段所指向的資源。我們可以打開 Vue 源碼中的 packages/vue/package.json 文件看一下: 

  1.  
  2.  "main": "index.js",  
  3.   "module": "dist/vue.runtime.esm-bundler.js",  

其中 module 字段指向的是 vue.runtime.esm-bundler.js 文件,意思就是說如果你的項目是使用 webpack 構建的,那你使用的 Vue 資源就是 vue.runtime.esm-bundler.js ,也就是說帶有 -bundler 字樣的 ESM 資源是給 rollup 或 webpack 等打包工具使用的,而帶有 -browser 字樣的 ESM 資源是直接給 <script type="module"> 去使用的。

那他們之間的區別是什么呢?那這就不得不提到上文中的 __DEV__ 常量,當構建用于 <script> c標簽的 ESM 資源時,如果是用于開發環境,那么 __DEV__ 會設置為 true;如果是用于生產環境,那么 __DEV__ 常量會被設置為 false ,從而被 Tree-Shaking 移除。但是當我們構建提供給打包工具的 ESM 格式的資源時,我們不能直接把 __DEV__ 設置為 true 或 false,而是使用(process.env.NODE_ENV !== 'production')替換掉 _DEV__常量。例如下面的源碼: 

  1. if (__DEV__) {  
  2.  warn(`useCssModule() is not supported in the global build.`)  

在帶有 -bundler 字樣的資源中會變成: 

  1. if ((process.env.NODE_ENV !== 'production')) {  
  2.   warn(`useCssModule() is not supported in the global build.`)  

這樣用戶側的 webpack 配置可以自己決定構建資源的目標環境,但是最終的效果其實是一樣的,這段代碼也只會出現在開發環境。

用戶除了可以直接使用 <script> 標簽引入資源,我們還希望用戶可以在 Node.js 中通過 require 語句引用資源,例如: 

  1. const Vue = require('vue') 

為什么會有這種需求呢?答案是服務端渲染,當服務端渲染時 Vue 的代碼是運行在 Node.js 環境的,而非瀏覽器環境,在 Node.js 環境下資源的模塊格式應該是 CommonJS ,簡稱 cjs。為了能夠輸出 cjs 模塊的資源,我們可以修改 rollup 的配置:format: 'cjs' 來實現: 

  1. // rollup.config.js  
  2. const config = {  
  3.   input: 'input.js',  
  4.   output: {  
  5.     file: 'output.js',  
  6.     format: 'cjs' // 指定模塊形式  
  7.   }  
  8.  
  9. export default config 

特性開關

在設計框架時,框架會提供諸多特性(或功能)給用戶,例如我們提供 A、B、C 三個特性給用戶,同時呢我們還提供了 a、b、c 三個對應的特性開關,用戶可以通過設置 a、b、c 為 true 和 false 來代表開啟和關閉,那么將會帶來很多收益:

對于用戶關閉的特性,我們可以利用 Tree-Shaking 機制讓其不包含在最終的資源中。

該機制為框架設計帶來了靈活性,可以通過特性開關任意為框架添加新的特性而不用擔心用不到這些特性的用戶側資源體積變大,同時當框架升級時,我們也可以通過特性開關來支持遺留的 API,這樣新的用戶可以選擇不適用遺留的 API,從而做到用戶側資源最小化。

那怎么實現特性開關呢?其實很簡單,原理和上文提到的 __DEV__ 常量一樣,本質是利用 rollup 的預定義常量插件來實現,那一段 Vue3 的 rollup 配置來看: 

  1.  
  2.  __FEATURE_OPTIONS_API__: isBundlerESMBuild ? `__VUE_OPTIONS_API__` : true,  

其中 __FEATURE_OPTIONS_API__ 類似于 __DEV__,我們可以在 Vue3 的源碼中搜索,可以找到很多類似如下代碼這樣的判斷分支: 

  1. // support for 2.x options  
  2. if (__FEATURE_OPTIONS_API__) {  
  3.   currentInstance = instance  
  4.   pauseTracking()  
  5.   applyOptions(instance, Component)  
  6.   resetTracking()  
  7.   currentInstance = null  

當 Vue 構建資源時,如果構建的資源是用于給打包工具使用的話(即帶有 -bundler 字樣的資源),那么上面代碼在資源中會變成: 

  1. // support for 2.x options  
  2. if (__VUE_OPTIONS_API__) { // 這一這里  
  3.   currentInstance = instance  
  4.   pauseTracking()  
  5.   applyOptions(instance, Component)  
  6.   resetTracking()  
  7.   currentInstance = null  

其中 __VUE_OPTIONS_API__ 就是一個特性開關,用戶側就可以通過設置 __VUE_OPTIONS_API__ 來控制是否包含這段代碼。通常用戶可以使用 webpack.DefinePlugin 插件實現: 

  1. // webpack.DefinePlugin 插件配置  
  2. new webpack.DefinePlugin({  
  3.   __VUE_OPTIONS_API__: JSON.stringify(true) // 開啟特性  
  4. }) 

最后再來詳細解釋一下 __VUE_OPTIONS_API__ 開關是干嘛用的,在 Vue2 中我們編寫的組件叫做組件選項 API: 

  1. export default {  
  2.  data() {}, // data 選項  
  3.   computed: {}, // computed 選項  
  4.  //  其他選項... 
  5.  

但是在 Vue3 中,更推薦使用 Composition API 來編寫代碼,例如: 

  1. export default {  
  2.  setup() {  
  3.   const count = ref(0)  
  4.     const doubleCount = computed(() => count.value * 2) // 相當于 Vue2 中的 computed 選項  
  5.  }  

但是為了兼容 Vue2,在 Vue3 中仍然可以使用選項 API 的方式編寫代碼,但是對于明確知道自己不會使用選項 API 的用戶來說,它們就可以選擇使用 __VUE_OPTIONS_API__ 開關來關閉該特性,這樣在打包的時候 Vue 的這部分代碼就不會包含在最終的資源中,從而減小資源體積。

錯誤處理

錯誤處理是開發框架的過程中非常重要的環節,框架的錯誤處理做的好壞能夠直接決定用戶應用程序的健壯性,同時還決定了用戶開發應用時處理錯誤的心智負擔。

為了讓大家對錯誤處理的重要性有更加直觀的感受,我們從一個小例子說起。假設我們開發了一個工具模塊,代碼如下: 

  1. // utils.js  
  2. export default {  
  3.   foo(fn) {  
  4.     fn && fn()  
  5.   }  

該模塊導出一個對象,其中 foo 屬性是一個函數,接收一個回調函數作為參數,調用 foo 函數時會執行回調函數,在用戶側使用時: 

  1. import utils from 'utils.js'  
  2. utils.foo(() => {  
  3.   // ...  
  4. }) 

大家思考一下如果用戶提供的回調函數在執行的時候出錯了怎么辦?此時有兩個辦法,其一是讓用戶自行處理,這需要用戶自己去 try...catch: 

  1. import utils from 'utils.js'  
  2. utils.foo(() => {  
  3.   try {  
  4.    // ...  
  5.   } catch (e) {  
  6.    // ... 
  7.  }  
  8. }) 

但是這對用戶來說是增加了負擔,試想一下如果 utils.js 不是僅僅提供了一個 foo 函數,而是提供了幾十上百個類似的函數,那么用戶在使用的時候就需要逐一添加錯誤處理程序。

第二種辦法是我們代替用戶統一處理錯誤,如下代碼所示: 

  1. // utils.js  
  2. export default {  
  3.   foo(fn) {  
  4.     try {  
  5.       fn && fn()  
  6.     } catch(e) {/* ... */}  
  7.   },  
  8.   bar(fn) {  
  9.     try {  
  10.       fn && fn()   
  11.     } catch(e) {/* ... */}  
  12.   },  

這種辦法其實就是我們代替用戶編寫錯誤處理程序,實際上我們可以進一步封裝錯誤處理程序為一個函數,假設叫它 ·callWithErrorHandling·: 

  1. // utils.js  
  2. export default {  
  3.   foo(fn) {  
  4.     callWithErrorHandling(fn)  
  5.   }, 
  6.   bar(fn) {  
  7.     callWithErrorHandling(fn)  
  8.   },  
  9.  
  10. function callWithErrorHandling(fn) {  
  11.   try {  
  12.     fn && fn()  
  13.   } catch (e) {  
  14.     console.log(e)  
  15.   }  

可以看到代碼變得簡潔多了,但簡潔不是目的,這么做真正的好處是,我們有機會為用戶提供統一的錯誤處理接口,如下代碼所示: 

  1. // utils.js  
  2. let handleError = null  
  3. export default {  
  4.   foo(fn) {  
  5.     callWithErrorHandling(fn)  
  6.   },  
  7.   // 用戶可以調用該函數注冊統一的錯誤處理函數  
  8.   resigterErrorHandler(fn) {  
  9.     handleError = fn  
  10.   } 
  11.  
  12. function callWithErrorHandling(fn) {  
  13.   try {  
  14.     fn && fn()  
  15.   } catch (e) {  
  16.     // 捕獲到的錯誤傳遞給用戶的錯誤處理程序  
  17.     handleError(e)  
  18.   }  

我們提供了 resigterErrorHandler 函數,用戶可以使用它注冊錯誤處理程序,然后在 callWithErrorHandling 函數內部捕獲到錯誤時,把錯誤對象傳遞給用戶注冊的錯誤處理程序。

這樣在用戶側的代碼就會非常簡潔且健壯: 

  1. import utils from 'utils.js'  
  2. // 注冊錯誤處理程序  
  3. utils.resigterErrorHandler((e) => {  
  4.   console.log(e)  
  5. }) 
  6. utils.foo(() => {/*...*/})  
  7. utils.bar(() => {/*...*/}) 

這時錯誤處理的能力完全由用戶控制,用戶既可以選擇忽略錯誤,也可以調用上報程序將錯誤上報到監控系統。

實際上這就是 Vue 錯誤處理的原理,你可以在源碼中搜索到 callWithErrorHandling 函數,另外在 Vue 中我們也可以注冊統一的錯誤處理函數: 

  1. import App from 'App.vue'  
  2. const app = createApp(App)  
  3. app.config.errorHandler = () => {  
  4.   // 錯誤處理程序  

良好的 Typescript 類型支持

Typescript 是微軟開源的編程語言,簡稱 TS,它是 JS 的超集能夠為 JS 提供類型支持。現在越來越多的人和團隊在他們的項目中使用 TS 語言,使用 TS 的好處很多,如代碼即文檔、編輯器的自動提示、一定程度上能夠避免低級 bug、讓代碼的可維護性更強等等。因此對 TS 類型支持的是否完善也成為評價一個框架的重要指標。

那如何衡量一個框架對 TS 類型支持的好壞呢?這里有一個常見的誤區,很多同學以為只要是使用 TS 編寫就是對 TS 類型支持的友好,其實使用 TS 編寫框架和框架對 TS 類型支持的友好是兩件關系不大的事兒。考慮到有的同學可能沒有接觸過 TS,所以這里不會做深入討論,我們只舉一個簡單的例子,如下是使用 TS 編寫的函數: 

  1. function foo(val: any) {  
  2.   return val  

這個函數很簡單,它接受一個參數 val 并且參數可以是任意類型(any),該函數直接將參數作為返回值,這說明返回值的類型是由參數決定的,參數如果是 number 類型那么返回值也是 number 類型,然后我們可以嘗試使用一下這個函數,如下圖所示:

類型支持不友好

在調用 foo 函數時我們傳遞了一個字符串類型的參數 'str',按照之前的分析,我們得到的結果 res 的類型應該也是字符串類型,然而當我們把鼠標 hover 到 res 常量上時可以看到其類型是 any,這并不是我們想要的結果,為了達到理想狀態我們只需要對 foo 函數做簡單的修改即可: 

  1. function foo<T extends any>(val: T): T {  
  2.   return val  

大家不需要理解這段代碼,我們直接來看一下現在的表現:

圖片類型友好

可以看到 res 的類型是字符字面量 'str' 而不是 any 了,這說明我們的代碼生效了。

通過這個簡單的例子我們認識到,使用 TS 編寫代碼與對 TS 類型支持友好是兩件事,在編寫大型框架時想要做到完美的 TS 類型支持是一件很不容易的事情,大家可以查看 Vue 源碼中的 runtime-core/src/apiDefineComponent.ts 文件,整個文件里真正會在瀏覽器運行的代碼其實只有 3 行,但是當你打開這個文件的時候你會發現它整整有接近 200 行的代碼,其實這些代碼都是在做類型支持方面的事情,由此可見框架想要做到完善的類型支持是需要付出相當大的努力的。 

 

責任編輯:龐桂玉 來源: 前端大全
相關推薦

2020-12-31 10:47:03

開發Vuejs技術

2016-01-18 10:06:05

編程

2021-07-28 07:01:09

薅羊毛架構Vue+SSR

2020-11-04 07:13:57

數據工程代碼編程

2015-09-06 16:03:57

2010-01-25 17:14:09

2022-03-27 09:06:04

React類型定義前端

2020-02-22 15:01:51

后端前端開發

2020-10-13 18:10:46

Kubernetes容器化云計算

2012-07-12 00:22:03

創業產品

2023-11-24 13:24:14

CIOOptus

2021-10-11 09:55:58

Facebook業務中斷網絡安全

2013-08-19 12:46:27

2024-04-12 08:54:13

從庫數據庫應用

2020-03-05 17:38:19

物聯網安全網絡安全

2021-07-26 07:47:36

C# 工作面試

2023-11-29 07:29:28

ReactSolid

2021-10-25 05:43:40

前端技術編程

2020-09-25 06:32:25

前端

2020-10-30 12:40:04

Reac性能優化
點贊
收藏

51CTO技術棧公眾號

√…a在线天堂一区| av毛片在线播放| youjizz亚洲| 日韩视频在线观看一区二区| 久久久亚洲精品无码| 777久久精品| 久久精品视频在线| 伊人网在线视频| 午夜精品久久99蜜桃的功能介绍| 性做久久久久久免费观看欧美| 欧美三级华人主播| 欧美午夜一区| 国产精品久久久亚洲| 写真福利精品福利在线观看| 欧美精品日韩精品| 欧美xxx.com| 久久精品人人做人人综合| 精品国产乱码久久久久软件| 在线精品一区二区| 国产精品国模在线| 欧美激情99| 国产成人精品久久二区二区91| 日韩人体视频| 国产成人综合精品在线| 亚洲人亚洲人色久| 日本aⅴ大伊香蕉精品视频| 成人18夜夜网深夜福利网| 尤物tv国产一区| 日韩欧美精品电影| 久久国产精品久久久久久久久久 | 3344国产永久在线观看视频| 亚洲精品国产欧美| 唐人社导航福利精品| 国产精品久久久久久久久快鸭 | 加勒比色老久久爱综合网| 欧美wwwxxxx| 国产精品白丝久久av网站| 久久亚洲精品毛片| 欧美激情网站| www国产精品com| 欧美激情第二页| 中文字幕乱码免费| 成人国产电影网| 日韩电影免费| 精品久久99ma| 亚洲精品亚洲人成在线| 日韩国产美国| 久久精品欧美一区二区三区麻豆| 美国成人av| 亚洲深夜福利视频| 日韩欧美二区| aaa毛片在线观看| 91精品国产高清一区二区三区| 日韩精品一级| 国模精品娜娜一二三区| 日本一区二区三区久久久久久久久不| 在线观看免费视频一区二区三区| 日韩av网址在线| 亚洲国产精品一区制服丝袜| 国产一线二线三线女| 一区二区成人在线观看| 性感美女一区二区在线观看| 欧美精品videos性欧美| 精品国产乱码久久久久久1区2匹| 欧美日韩一区二区视频在线观看| 欧美另类专区| av在线不卡播放| 亚洲丁香婷深爱综合| 国产精品视频一区视频二区| www.日韩系列| 国产视频一区欧美| 一路向西2在线观看| 日韩一区二区中文字幕| 图片婷婷一区| 亚洲伦理电影| 久久久精品视频成人| 欧美自拍偷拍| 青青草精品视频在线| 欧美日韩在线二区| 欧美福利专区| 日韩欧美小视频| 精品中文字幕视频| 精品伊人久久久久7777人| 在线播放你懂得| 久久久噜噜噜久久| 国产一区二区视频在线播放| 羞羞在线视频| 欧美成人免费网| 亚洲黄网站黄| 成年女人在线看片| 成人18视频| 欧美日韩国产黄| 88久久精品| 日本我和搜子同居的日子高清在线| 久久免费少妇高潮久久精品99| 久久久久久久久久久电影| www.久久久久.com| 国产成人精品免费看在线播放| 五月综合激情婷婷六月色窝| 你懂的视频欧美| 国产香蕉在线| 欧美 日韩 国产 在线观看| 精品国内产的精品视频在线观看| 美女www一区二区| 日韩三级影视| 国产 欧美 日韩 一区| 在线精品播放av| 国产欧美日韩激情| 久久精品亚洲人成影院 | 香蕉av在线| 国产激情在线看| 国内精品视频久久| 日韩一区二区三区av| 粉嫩av亚洲一区二区图片| 日韩av密桃| 日本一区视频| 国产日产精品一区二区三区四区| 亚洲欧美激情精品一区二区| 午夜精品久久久久久久蜜桃app| 蜜桃精品视频在线| 国内精品麻豆美女在线播放视频| 欧美bbbxxxxx| 欧美日韩在线精品一区二区三区激情综 | 在线欧美一级视频| 国产精品欧美风情| 精品中文字幕视频| 午夜一区二区三区视频| 99久久久久免费精品国产 | 91女厕偷拍女厕偷拍高清| 午夜在线视频观看日韩17c| 成人性生交大片免费观看网站| 激情在线视频| 黄色三级电影网站| 亚洲另类第一页| 国产在线青青草| 国产免费一区二区视频| 色噜噜狠狠一区二区三区| 草莓视频一区| 欧美一区二区大胆人体摄影专业网站| 亚洲成人av中文字幕| 日韩精品中午字幕| 精品国产乱码久久久久久免费| 欧美日韩一区二区三区四区五区| 欧美视频一二三| 久久久九九九九| 久久久.com| 夜夜揉揉日日人人青青一国产精品 | 亚洲精品美国一| 国产日韩欧美高清| 久久婷婷一区| 精品三级在线观看视频| 日韩视频1区| 婷婷成人在线| 欧美电影《轻佻寡妇》| 亚洲精品美女| 肉色丝袜一区二区| 国内精品写真在线观看| 国产精品自拍毛片| 奇米一区二区三区av| 国产毛片精品视频| 成人国产精品免费网站| 国产成人综合在线播放| 国产精品午夜久久| 日本高清无吗v一区| 国产精品xx| 精品国产一区久久| 伊人av综合网| 国产精品成久久久久三级 | 日本一区精品视频| 色88久久久久高潮综合影院| 久久精品高清| 日韩中文字幕av电影| 国产精品中文欧美| 成人天堂噜噜噜| 亚洲欧洲国产一区| 欧美成年人在线观看| 国产精品嫩草99av在线| 波多野结衣一区二区三区免费视频| 国产精品成人**免费视频| 成人精品视频| 国产成人久久精品77777最新版本| 中文字幕人成不卡一区| 欧美一区国产二区| 亚洲免费高清视频| 在线电影一区二区| 三级毛片在线免费看| 四虎影院在线域名免费观看| 色婷婷成人网| 亚洲国产午夜| 日韩欧美国产网站| 欧美一性一乱一交一视频| 91麻豆天美传媒在线| 色婷婷视频在线观看| 欧美日韩国产精品一区二区亚洲| 国产精品美女久久久久久久| 久久久电影免费观看完整版| 五月天激情图片| 蜜桃精品在线| 久久综合九色综合97婷婷女人| 亚洲第一黄色网|