精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

我從Vuejs中學(xué)到了什么

開發(fā) 前端
本節(jié)內(nèi)容需要大家對常用的模塊打包工具有一定的使用經(jīng)驗(yàn),尤其是 rollup.js 以及 webpack。

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

本節(jié)內(nèi)容需要大家對常用的模塊打包工具有一定的使用經(jīng)驗(yàn),尤其是 rollup.js 以及 webpack。如果你只用過或了解過其中一個也沒關(guān)系,因?yàn)樗鼈兒芏喔拍钇鋵?shí)是類似的。如果你沒有使用任何模塊打包工具那么需要你自行去了解一下,至少有了初步認(rèn)識之后再來看本節(jié)內(nèi)容會更好一些。

提升用戶的開發(fā)體驗(yàn)

衡量一個框架是否足夠優(yōu)秀的指標(biāo)之一就是看它的開發(fā)體驗(yàn)如何,我們拿 Vue3 舉個例子:

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

當(dāng)我們創(chuàng)建一個 Vue 應(yīng)用并試圖將其掛載到一個不存在的 DOM 節(jié)點(diǎn)時就會得到一個警告信息:

warn

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

所以在框架設(shè)計和開發(fā)的過程中,提供友好的警告信息是至關(guān)重要的,如果這一點(diǎn)做得不好那么很可能經(jīng)常收到用戶的抱怨。始終提供友好的警告信息不僅能夠快速幫助用戶定位問題,節(jié)省用戶的時間,還能夠?yàn)榭蚣苁斋@良好的口碑,讓用戶認(rèn)為你是非常專業(yè)的。

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

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

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

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

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

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

沒有任何處理的輸出

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

然后刷新瀏覽器后查看控制臺,會發(fā)現(xiàn)輸出的內(nèi)容變得非常直觀:

控制框架代碼的體積

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

如果我們?nèi)タ?Vue 的源碼會發(fā)現(xiàn),每一個 warn() 函數(shù)的調(diào)用都會配合 __DEV__ 常量的檢查,例如: 

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

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

Vue 使用的是 rollup.js 對項(xiàng)目進(jìn)行構(gòu)建的,這里的 __DEV__ 常量實(shí)際上是通過 rollup 的配置來預(yù)定義的,其功能類似于 webpack 中的 DefinePlugin 插件。

Vue 在輸出資源的時候,會輸出兩個版本的資源,其中一個資源用于開發(fā)環(huán)境,如 vue.global.js ;另一個與其對應(yīng)的用于生產(chǎn)環(huán)境,如:vue.global.prod.js ,通過文件名稱我們也能夠區(qū)分。

當(dāng) Vue 構(gòu)建用于開發(fā)環(huán)境的資源時,會把 __DEV__ 常量設(shè)置為 true,這時上面那段輸出警告信息的代碼就等價于: 

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

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

當(dāng) Vue 構(gòu)建用于生產(chǎn)環(huán)境的資源時,會把 __DEV__ 常量設(shè)置為 false,這時上面那段輸出警告信息的代碼就等價于: 

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

可以看到 __DEV__ 常量被替換為字面量 false ,這時我們發(fā)現(xiàn)這段分支代碼永遠(yuǎn)都不會執(zhí)行,因?yàn)榕袛鄺l件始終為假,這段永遠(yuǎn)不會執(zhí)行的代碼被稱為 Dead Code,它不會出現(xiàn)在最終的產(chǎn)物中,在構(gòu)建資源的時候就會被移除,因此在 vue.global.prod.js 中是不會存在這段代碼的。

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

框架要做到良好的 Tree-Shaking

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

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

想要實(shí)現(xiàn) Tree-Shaking 必須滿足一個條件,即模塊必須是 ES Module,因?yàn)?Tree-Shaking 依賴 ESM 的靜態(tài)結(jié)構(gòu)。我們使用 rollup 通過一個簡單的例子看看 Tree-Shaking 如何工作,我們 demo 的目錄結(jié)構(gòu)如下: 

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

首先安裝 rollup:

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

下面是 input.js 和 utils.js 文件的內(nèi)容: 

  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 文件中定義并導(dǎo)出了兩個函數(shù),分別是 foo 和 bar,然后在 input.js 中導(dǎo)入了 foo 函數(shù)并執(zhí)行,注意我們并沒有導(dǎo)入 bar 函數(shù)。

接著我們執(zhí)行如下命令使用 rollup 構(gòu)建:

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

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

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

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

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

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

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

注意這段注釋代碼 /*#__PURE_*_/,該注釋的作用就是用來告訴 rollup 對于 foo() 函數(shù)的調(diào)用不會產(chǎn)生副作用,你可以放心的對其進(jìn)行 Tree-Shaking,此時再次執(zhí)行構(gòu)建命令并查看 bundle.js 文件你會發(fā)現(xiàn)它的內(nèi)容是空的,這說明 Tree-Shaking 生效了。

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

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

也許你會覺得這會不會對編寫代碼帶來很大的心智負(fù)擔(dān)?其實(shí)不會,這是因?yàn)橥ǔ.a(chǎn)生副作用的代碼都是模塊內(nèi)函數(shù)的頂級調(diào)用,什么是頂級調(diào)用呢?如下代碼所示: 

  1. foo() // 頂級調(diào)用  
  2. function bar() {  
  3.   foo() // 函數(shù)內(nèi)調(diào)用  

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

框架應(yīng)該輸出怎樣的構(gòu)建產(chǎn)物

上文中我們提到 Vue 會為開發(fā)環(huán)境和生產(chǎn)環(huán)境輸出不同的包,例如 vue.global.js 用于開發(fā)環(huán)境,它包含了必要的警告信息,而 vue.global.prod.js 用于生產(chǎn)環(huán)境,不包含警告信息。實(shí)際上 Vue 的構(gòu)建產(chǎn)物除了有環(huán)境上的區(qū)分之外,還會根據(jù)使用場景的不同而輸出其他形式的產(chǎn)物,這一節(jié)我們將討論這些產(chǎn)物的用途以及在構(gòu)建階段如何輸出這些產(chǎn)物。

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

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

為了能夠?qū)崿F(xiàn)這個需求,我們就需要輸出一種叫做 IIFE 格式的資源,IIFE 的全稱是 Immediately Invoked Function Expression ,即”立即調(diào)用的函數(shù)表達(dá)式“,可以很容易的用 JS 來表達(dá): 

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

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

在 rollup 中我們可以通過配置 format: 'iife' 來實(shí)現(xiàn)輸出這種形式的資源: 

  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 

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

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

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

你可能已經(jīng)注意到了,為什么 vue.esm-browser.js 文件中會有 -browser 字樣,其實(shí)對于 ESM 格式的資源來說,Vue 還會輸出一個 vue.esm-bundler.js 文件,其中 -browser 變成了 -bundler。為什么這么做呢?我們知道無論是 rollup 還是 webpack 在尋找資源時,如果 package.json 中存在 module 字段,那么會優(yōu)先使用 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 文件,意思就是說如果你的項(xiàng)目是使用 webpack 構(gòu)建的,那你使用的 Vue 資源就是 vue.runtime.esm-bundler.js ,也就是說帶有 -bundler 字樣的 ESM 資源是給 rollup 或 webpack 等打包工具使用的,而帶有 -browser 字樣的 ESM 資源是直接給 <script type="module"> 去使用的。

那他們之間的區(qū)別是什么呢?那這就不得不提到上文中的 __DEV__ 常量,當(dāng)構(gòu)建用于 <script> c標(biāo)簽的 ESM 資源時,如果是用于開發(fā)環(huán)境,那么 __DEV__ 會設(shè)置為 true;如果是用于生產(chǎn)環(huán)境,那么 __DEV__ 常量會被設(shè)置為 false ,從而被 Tree-Shaking 移除。但是當(dāng)我們構(gòu)建提供給打包工具的 ESM 格式的資源時,我們不能直接把 __DEV__ 設(shè)置為 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.`)  

這樣用戶側(cè)的 webpack 配置可以自己決定構(gòu)建資源的目標(biāo)環(huán)境,但是最終的效果其實(shí)是一樣的,這段代碼也只會出現(xiàn)在開發(fā)環(huán)境。

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

  1. const Vue = require('vue') 

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

  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 

特性開關(guān)

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

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

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

那怎么實(shí)現(xiàn)特性開關(guān)呢?其實(shí)很簡單,原理和上文提到的 __DEV__ 常量一樣,本質(zhì)是利用 rollup 的預(yù)定義常量插件來實(shí)現(xiàn),那一段 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  

當(dāng) Vue 構(gòu)建資源時,如果構(gòu)建的資源是用于給打包工具使用的話(即帶有 -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__ 就是一個特性開關(guān),用戶側(cè)就可以通過設(shè)置 __VUE_OPTIONS_API__ 來控制是否包含這段代碼。通常用戶可以使用 webpack.DefinePlugin 插件實(shí)現(xiàn): 

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

最后再來詳細(xì)解釋一下 __VUE_OPTIONS_API__ 開關(guān)是干嘛用的,在 Vue2 中我們編寫的組件叫做組件選項(xiàng) API: 

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

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

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

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

錯誤處理

錯誤處理是開發(fā)框架的過程中非常重要的環(huán)節(jié),框架的錯誤處理做的好壞能夠直接決定用戶應(yīng)用程序的健壯性,同時還決定了用戶開發(fā)應(yīng)用時處理錯誤的心智負(fù)擔(dān)。

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

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

該模塊導(dǎo)出一個對象,其中 foo 屬性是一個函數(shù),接收一個回調(diào)函數(shù)作為參數(shù),調(diào)用 foo 函數(shù)時會執(zhí)行回調(diào)函數(shù),在用戶側(cè)使用時: 

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

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

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

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

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

  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.   },  

這種辦法其實(shí)就是我們代替用戶編寫錯誤處理程序,實(shí)際上我們可以進(jìn)一步封裝錯誤處理程序?yàn)橐粋€函數(shù),假設(shè)叫它 ·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.   }  

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

  1. // utils.js  
  2. let handleError = null  
  3. export default {  
  4.   foo(fn) {  
  5.     callWithErrorHandling(fn)  
  6.   },  
  7.   // 用戶可以調(diào)用該函數(shù)注冊統(tǒng)一的錯誤處理函數(shù)  
  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 函數(shù),用戶可以使用它注冊錯誤處理程序,然后在 callWithErrorHandling 函數(shù)內(nèi)部捕獲到錯誤時,把錯誤對象傳遞給用戶注冊的錯誤處理程序。

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

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

這時錯誤處理的能力完全由用戶控制,用戶既可以選擇忽略錯誤,也可以調(diào)用上報程序?qū)㈠e誤上報到監(jiān)控系統(tǒng)。

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

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

良好的 Typescript 類型支持

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

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

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

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

類型支持不友好

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

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

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

圖片類型友好

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

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

 

責(zé)任編輯:龐桂玉 來源: 前端大全
相關(guān)推薦

2020-12-31 10:47:03

開發(fā)Vuejs技術(shù)

2016-01-18 10:06:05

編程

2021-07-28 07:01:09

薅羊毛架構(gòu)Vue+SSR

2020-11-04 07:13:57

數(shù)據(jù)工程代碼編程

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

后端前端開發(fā)

2020-10-13 18:10:46

Kubernetes容器化云計算

2012-07-12 00:22:03

創(chuàng)業(yè)產(chǎn)品

2023-11-24 13:24:14

CIOOptus

2021-10-11 09:55:58

Facebook業(yè)務(wù)中斷網(wǎng)絡(luò)安全

2013-08-19 12:46:27

2024-04-12 08:54:13

從庫數(shù)據(jù)庫應(yīng)用

2020-03-05 17:38:19

物聯(lián)網(wǎng)安全網(wǎng)絡(luò)安全

2021-07-26 07:47:36

C# 工作面試

2023-11-29 07:29:28

ReactSolid

2020-09-25 06:32:25

前端

2021-10-25 05:43:40

前端技術(shù)編程

2020-10-30 12:40:04

Reac性能優(yōu)化
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號

国产精品成人3p一区二区三区| 亚洲av无码乱码国产精品| 精品av一区二区| 91久久精品一区二区三| 日韩理论片在线观看| 99精品人妻国产毛片| 亚洲精品v亚洲精品v日韩精品| 亚洲日本成人在线观看| 国产一区香蕉久久| 激情五月少妇a| 色橹橹欧美在线观看视频高清 | 国产日韩精品视频| 国产精品成人69xxx免费视频| 欧美.com| 欧美午夜美女看片| 在线国产精品网| 亚洲精品久久久久久久久久| 免费在线播放第一区高清av| 色狠狠av一区二区三区香蕉蜜桃| 成人三级做爰av| 2020国产在线| 中文字幕不卡三区| 国产伦精品一区二区三区视频孕妇| 极品国产91在线网站| 欧洲杯什么时候开赛| 日韩视频在线观看一区二区| 大j8黑人w巨大888a片| 网友自拍视频在线| 成人免费高清在线| 国产在线不卡精品| 亚洲精品www久久久久久| 久久中文视频| 亚洲第一精品久久忘忧草社区| 日本一极黄色片| 羞羞网站在线看| 中文字幕欧美激情一区| 国产精品久久久久久久久久久久冷 | 国产91在线播放九色| 久久aimee| 91精品国产品国语在线不卡| 37pao成人国产永久免费视频| 在线观看电影av| 欧美国产精品v| 精品欧美国产| 国产91免费看| 国产福利不卡视频| 成人欧美一区二区三区在线湿哒哒 | 精品在线播放视频| 亚洲精品小说| 中文字幕在线观看日韩| 国产亚洲无码精品| 综合欧美亚洲| 日本韩国精品一区二区在线观看| 免费看黄在线看| 1stkiss在线漫画| 国产精品网曝门| 日本精品一区二区| 日本天堂在线| 99久久婷婷国产综合精品电影| 97久久天天综合色天天综合色hd | 免费看成人哺乳视频网站| 精品久久国产字幕高潮| 在线观看免费视频污| 鲁鲁在线中文| 午夜一区二区三区视频| 毛片av在线播放| 生活片a∨在线观看| 国产视频视频一区| 日本电影一区二区三区| 青青草在线播放| 久久夜色精品国产欧美乱极品| 国产亚洲欧美一区二区三区| 亚洲精品国产精| 国产成人综合视频| av一区观看| 亚洲黄色a级片| 国产不卡免费视频| 国产精品加勒比| 免费观看国产精品| 99久久精品免费看国产免费软件| 国产精品免费一区二区| 亚洲欧美激情在线观看| 成人久久久精品乱码一区二区三区 | 久久99精品久久久| 91社区国产高清| 日本妇乱大交xxxxx| 日韩av网站在线观看| 国产精品欧美日韩久久| 亚洲网站免费观看| 久久久久国产精品一区二区| 国产精品欧美日韩| 成人午夜福利视频| 久久久精品国产免大香伊 | 毛片免费不卡| 亚洲成人激情自拍| 中文字幕在线观看第三页| 精品国产三级| 亚洲欧美日韩一区二区三区在线| 成人午夜免费影院| 91久久夜色精品国产九色| 国产精品偷伦免费视频观看的| av在线资源观看| 久久久综合视频| 加勒比海盗1在线观看免费国语版| 欧美13videosex性极品| 884aa四虎影成人精品一区| 久久久久久久久久影视| 欧美色网址大全| 欧美劲爆第一页| 中文字幕免费高清在线观看| 丁香六月久久综合狠狠色| 日本一区精品| 国产区美女在线| 欧美精品99久久久**| 天天插天天射天天干| 97精品国产福利一区二区三区| 久久久久久欧美| 国产三级第一页| 欧美韩日一区二区三区| 免费观看国产精品视频| 欧美一区在线观看视频| 在线日韩第一页| www.国产色| 狠狠色狠狠色综合系列| 日本高清视频一区二区三区 | 成人情趣视频网站| 久久99久久亚洲国产| 久久国产香蕉视频| 91在线国产福利| 久操手机在线视频| 热久久久久久| 日韩在线观看免费网站| 一级黄色在线视频| 97久久精品人人澡人人爽| 国产精品8888| 高清一区二区三区av| 2020日本不卡一区二区视频| 中文字幕精品—区二区日日骚| 欧洲一区精品| 亚洲国产精品高清久久久| 免费中文字幕在线| 精品亚洲成av人在线观看| 色999五月色| 东京一区二区| 日韩精品免费看| 国产成人无码精品久在线观看| 粉嫩嫩av羞羞动漫久久久| 色哟哟免费网站| 成人亚洲精品| 欧美日韩成人网| 性生交大片免费看女人按摩| 一区二区三区中文字幕| 男人添女人荫蒂国产| 国产一在线精品一区在线观看| 91亚洲国产成人精品性色| 黄色片网站在线| 91精品国产综合久久精品app| 国产免费一区二区三区四区| 久久99久久精品| 一区二区不卡视频| 粉嫩一区二区三区在线观看| 欧美另类极品videosbestfree| a在线观看视频| 亚洲一区二区三区四区在线免费观看 | 国产v综合v亚洲欧| 国产一线二线三线女| 国产乱人伦精品一区| 97色在线视频观看| 欧美在线观看在线观看| 在线观看免费成人| 精品视频第一页| 国产精品123| 国产在线播放观看| 国产欧美久久一区二区三区| 国产精品久久久久久av福利软件 | 久久这里只精品| 91精品国产91久久久久久黑人| 91中文在线观看| 精品精品导航| 亚洲日韩中文字幕在线播放| 中国女人一级一次看片| 中文字幕一区二区三区精华液| av在线免费观看不卡| 日韩午夜在线电影| 欧美一区二区视频在线| www.久久爱.com| 久久久久久久久亚洲| 四虎影院在线域名免费观看| 91福利在线导航| 农村黄色一级片| 26uuu久久天堂性欧美| 亚洲精品20p| 亚洲精品欧洲| 一区二区三区的久久的视频| 成人涩涩网站| 国产乱人伦真实精品视频| 欧美一卡二卡| 中文字幕在线看视频国产欧美| 免费观看成年人视频| 欧美日韩综合色| 日本一区二区三区四区五区| 中文字幕一区二区三区在线播放| 99久久人妻无码中文字幕系列| 免费看欧美美女黄的网站| 国内精品视频一区二区三区| 欧美丰满日韩| 欧美成ee人免费视频| 日本久久伊人| 国产精品视频大全| 欧美三级网站| 欧美裸体男粗大视频在线观看| 二区三区在线| 亚洲精品理论电影| www日本高清| 欧美日韩不卡在线| 无码人妻aⅴ一区二区三区有奶水| 亚洲免费观看高清完整版在线观看熊| www在线观看免费视频| 不卡在线观看av| av地址在线观看| 激情深爱一区二区| 亚洲综合色在线观看| 国产日韩精品视频一区二区三区| 韩国无码av片在线观看网站| 久久一区二区三区喷水| 日本一区免费| 九九久久婷婷| 蜜桃传媒视频麻豆第一区免费观看 | 美女100%无挡| 99久久综合狠狠综合久久| 亚洲精品一二三四| 国产揄拍国内精品对白| 久热精品在线观看视频| 日韩高清在线一区| 少妇高清精品毛片在线视频| 99视频+国产日韩欧美| 日韩成人三级视频| 影音先锋在线一区| 九九热只有这里有精品| 欧美日韩国产在线一区| 亚洲av首页在线| 一区二区三区午夜视频| 艳母动漫在线免费观看| 久久精品久久久| 中文字幕欧美日韩一区二区三区 | 久久久久久久91| 五月婷婷视频在线观看| 美女撒尿一区二区三区| 91在线中文| 欧美国产日韩一区二区| 激情网站在线| 91成人精品网站| 波多野结衣久久精品| 国产成人+综合亚洲+天堂| 亚洲不卡系列| 国产综合在线观看视频| 免费一级欧美在线大片| 国产 高清 精品 在线 a | 精品成人佐山爱一区二区| 亚洲乱码精品久久久久..| 亚洲国产成人av在线| 天天操天天操天天| 亚洲欧洲午夜一线一品| 在线观看免费版| 久久精品久久久久久国产 免费| 欧美三级电影一区二区三区| 欧美成人中文字幕| 丁香花在线电影| 国产91免费看片| 国产资源一区| 91美女片黄在线观| 久久香蕉网站| 日韩一区免费观看| 综合精品久久| 免费在线激情视频| 久99久精品视频免费观看| 无码人妻久久一区二区三区蜜桃| www.日韩av| 快灬快灬一下爽蜜桃在线观看| 亚洲日穴在线视频| 日韩免费一级片| 欧美色图在线观看| 亚洲精品97久久中文字幕| 亚洲视频视频在线| 亚洲妇熟xxxx妇色黄| 秋霞午夜一区二区| 91成人福利社区| 久久精品午夜一区二区福利| 日韩一区亚洲二区| 一本久道高清无码视频| 巨乳诱惑日韩免费av| 丰满饥渴老女人hd| 久久精品综合网| 免费网站看av| 欧美丝袜丝nylons| 手机看片1024日韩| 精品国偷自产在线视频| 麻豆国产在线| 成人乱色短篇合集| 欧美一区自拍| 4444亚洲人成无码网在线观看| 久热综合在线亚洲精品| 女性生殖扒开酷刑vk| 国产精品色哟哟| 午夜毛片在线观看| 欧美电影免费提供在线观看| 国产三级在线| 97视频国产在线| 亚洲小说春色综合另类电影| 日韩国产在线一区| 亚洲一区不卡| 午夜不卡久久精品无码免费| 国产精品福利一区二区三区| www亚洲视频| 亚洲电影免费观看高清完整版在线观看 | 久久精品成人欧美大片古装| 樱花草涩涩www在线播放| 电影午夜精品一区二区三区| 我不卡神马影院| 天天干天天爽天天射| 久久久国产午夜精品| 国产精品成人网站| 日韩美女视频一区二区在线观看| lutube成人福利在线观看| 欧美专区在线观看| 欧美日韩看看2015永久免费| 日本大片免费看| 国产一区二区成人久久免费影院 | 欧美大片一区| 夜夜爽久久精品91| 国产精品电影一区二区| 中文字幕 人妻熟女| 亚洲热线99精品视频| 超碰91在线观看| 超碰97网站| 黑丝一区二区| 制服丝袜在线第一页| 亚洲精品久久久蜜桃| 国产农村妇女毛片精品| 日韩在线观看免费全| 农村妇女一区二区| 亚洲人成网站在线观看播放| 蜜桃传媒麻豆第一区在线观看| 妺妺窝人体色WWW精品| 在线视频亚洲一区| 成人p站proumb入口| 国产精品欧美风情| 99re66热这里只有精品8| 日韩av.com| 亚洲欧美日韩国产一区二区三区| 国产乱码精品一区二区三区精东| 久久久国产精品免费| 国内精品视频| 精品人妻大屁股白浆无码| 成人黄色av网站在线| 欧美在线观看不卡| 国产亚洲激情在线| 九九热线视频只有这里最精品| 日本午夜精品一区二区三区| 美女视频黄久久| 国产中文av在线| 欧美不卡视频一区| 国产直播在线| 日韩.欧美.亚洲| 精品一区二区三区av| 久久精品一级片| 亚洲国产成人精品电影| 日韩色淫视频| 免费成人进口网站| 成人一区二区三区视频在线观看| 国产精品成人久久| 在线播放日韩av| 精品欧美视频| 内射国产内射夫妻免费频道| 国产清纯白嫩初高生在线观看91 | 精品视频在线观看一区| 91丝袜呻吟高潮美腿白嫩在线观看| 香蕉影院在线观看| 日韩在线播放视频| 久9re热视频这里只有精品| 欧美两根一起进3p做受视频| 亚洲卡通动漫在线| 亚洲 欧美 激情 小说 另类| 国产精品色婷婷视频| 在线看片一区| 国产激情av在线| 精品福利视频一区二区三区| 99久久婷婷国产综合精品首页 | 日韩制服一区| 国产内射老熟女aaaa| 91美女视频网站| 国产美女永久免费| 欧美做爰性生交视频| 综合天堂av久久久久久久| 亚洲AV无码国产成人久久| 欧美一区中文字幕| 写真福利精品福利在线观看| 日本美女爱爱视频| 亚洲国产精品黑人久久久| 蜜臀av在线观看|