精品欧美一区二区三区在线观看 _久久久久国色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ù)棧公眾號

免播放器亚洲一区| 草莓视频丝瓜在线观看丝瓜18| 伊人成人在线| 日韩精品极品视频免费观看| 久色视频在线播放| 三级国产在线观看| 秋霞av亚洲一区二区三| www.色综合| 久久久无码人妻精品无码| 国产黄a三级三级三级av在线看| 另类中文字幕网| 美女精品视频一区| 西西大胆午夜视频| 日本精品网站| 一区二区三区在线视频免费 | 久久久久久久人妻无码中文字幕爆| 日本中文字幕在线播放| 国产福利一区二区三区视频在线| 久久久久久久久久久成人| 一区二区不卡免费视频| 久久天堂影院| 亚洲国产综合91精品麻豆| 欧美精品与人动性物交免费看| 中文无码av一区二区三区| 婷婷激情图片久久| 欧美精品一区二区三区蜜桃| 成年人免费在线播放| 超碰国产在线| 成人激情文学综合网| 国产国语videosex另类| 免费一级肉体全黄毛片| 成人国产精品一级毛片视频| 欧美一区在线视频| 中文字幕乱码人妻综合二区三区| 天堂在线一二区| 久久99精品国产.久久久久| 韩国视频理论视频久久| 99热6这里只有精品| 精品国产亚洲一区二区三区在线| 富二代精品短视频| 亚洲小视频在线播放| 深夜福利免费在线观看| 国产成人高清视频| 国产精品美女www爽爽爽视频| 精品少妇久久久| 成人av国产| 亚洲美女在线视频| 国产综合内射日韩久| 外国成人毛片| 色先锋aa成人| 成熟丰满熟妇高潮xxxxx视频| av理论在线观看| 国产偷国产偷精品高清尤物| 欧美污视频久久久| 视频国产在线观看| 久久久久久久久久看片| 日韩高清国产一区在线观看| 成人高清网站| 国产精品电影一区二区| 一级特黄录像免费播放全99| 免费黄色网页在线观看| 自拍偷自拍亚洲精品播放| 国产高清免费在线| 羞羞视频在线免费国产| 亚洲综合免费观看高清完整版 | 欧美一级日韩免费不卡| 91国内在线播放| 国产精品成人3p一区二区三区| 在线播放欧美女士性生活| 亚洲无在线观看| 国产一区二区三区| 欧美成人官网二区| 欧美熟妇精品黑人巨大一二三区| 妖精视频一区二区三区| 在线播放日韩欧美| 欧美日韩色视频| 午夜国产精品视频| 97免费视频在线| 中文字幕手机在线视频| 精品一区二区三区在线观看国产| 91在线短视频| 亚洲日本香蕉视频| 国产女主播一区| 国产成人免费高清视频| 国内高清免费在线视频| 在线中文字幕不卡| 久久精品一二三四| 羞羞答答一区二区| 久久精品视频在线播放| 日本三级2019| 欧美a级一区二区| 国产不卡一区二区在线观看 | 久久久国产精品视频| 免看一级a毛片一片成人不卡| 一本色道久久| 成人免费观看网址| 亚洲AV成人无码一二三区在线| 国产清纯白嫩初高生在线观看91| 一道本在线观看视频| av在线视屏| 欧美日韩国产小视频| 青青草视频网站| 国产精品二区不卡| 77777少妇光屁股久久一区| 中文字幕av久久爽| 成人avav影音| 欧美性受xxxx黑人猛交88| 国产中文在线播放| 91精品啪在线观看国产60岁| 美国黄色a级片| 欧美一区国产在线| 国产成人精品在线播放| 成人免费一级视频| 国产精品美日韩| 2022亚洲天堂| 国产精品115| 精品国产一区二区三区四区在线观看 | 老司机凹凸av亚洲导航| 日韩视频精品在线| 成年人av网站| youjizz国产精品| 国产又粗又硬又长| 国产成人a视频高清在线观看| 337p日本欧洲亚洲大胆精品| 女人18毛片毛片毛片毛片区二 | 日韩成人精品一区| 26uuu久久噜噜噜噜| 国产高清精品软件丝瓜软件| 亚洲国产精品精华液ab| 欧美色图另类小说| 成人爽a毛片免费啪啪红桃视频| 久久网福利资源网站| 亚洲精品国产精品国自产网站按摩 | 国产精品白嫩美女在线观看 | 亚洲一区二区视频在线播放| 久久久精品中文字幕麻豆发布| 免费看毛片的网址| 91精品日本| 久热精品视频在线| 一级视频在线播放| 欧美国产日韩a欧美在线观看| 97av视频在线观看| 欧美性生活一级片| 欧美精品激情blacked18| 国产又粗又猛又黄又爽| 国产精品伦理在线| 9l视频白拍9色9l视频| av一区二区高清| 国产精品都在这里| 国产高清av在线| 欧美吻胸吃奶大尺度电影| av网在线播放| 日本特黄久久久高潮| 三区精品视频观看| 香蕉视频亚洲一级| 亚洲最新中文字幕| 亚洲手机在线观看| 亚洲欧美综合色| 色黄视频免费看| 激情视频一区| 久久99精品久久久久久久久久 | 成人免费网站在线| v片在线观看| 精品日韩一区二区| 国产性xxxx高清| 26uuu欧美| 日本va中文字幕| 波多野结衣在线观看一区二区三区| 国产精品自拍视频| 中文在线观看免费| 亚洲国产欧美一区二区三区久久| 欧美三日本三级少妇99| 国产性天天综合网| 亚洲精品免费一区亚洲精品免费精品一区 | 激情成人午夜视频| 国产精品自拍合集| av在线亚洲色图| 日本视频久久久| 免费黄色网址在线观看| 精品国产乱码久久久久久闺蜜| 97免费在线观看视频| 日本一区二区三级电影在线观看 | 国内精品久久久久久久久久 | 欧美成人高清视频| 日本人妻丰满熟妇久久久久久| 欧美视频裸体精品| 黑人操日本美女| 成人激情av网| 69久久久久久| 国内综合精品午夜久久资源| 你懂的视频在线一区二区| 天天综合91| 51久久精品夜色国产麻豆| av在线三区| 精品久久久久香蕉网| 丰满人妻一区二区三区四区| 有坂深雪av一区二区精品| 波多野结衣a v在线| 精品一区二区在线视频| 18禁男女爽爽爽午夜网站免费 | 久久久久久99精品| 国产伦精品一区二区三区妓女下载| 在线视频亚洲| 99精品视频网站| 嫩草一区二区三区| 99re在线国产| 亚洲毛片在线免费| 欧美在线播放视频| 污片在线免费观看| 永久免费看mv网站入口亚洲| 日本免费不卡视频| 欧美一级搡bbbb搡bbbb| 中文亚洲av片在线观看| 欧美丝袜美女中出在线| 欧美日韩大片在线观看| 国产欧美精品国产国产专区| 人妻无码中文久久久久专区| 国产一区二区中文字幕| 8x8x最新地址| 新狼窝色av性久久久久久| 久操手机在线视频| 羞羞答答成人影院www| 日本不卡二区高清三区| 三级小说欧洲区亚洲区| 国产精品国产精品| 秋霞一区二区| 亚洲在线观看视频| 色诱色偷偷久久综合| 国产精品久久久久影院日本| 男人av在线播放| 久久久久国产视频| av色综合久久天堂av色综合在| 亚洲美女激情视频| 四虎影视2018在线播放alocalhost| 精品免费一区二区三区| 国产毛片在线视频| 欧美放荡的少妇| 怡红院男人天堂| 欧美日韩日本国产| 天堂网中文在线观看| 中日韩av电影| 伊人影院综合网| 国产肉丝袜一区二区| 日韩中文字幕电影| 99久久国产综合精品色伊| 一级黄色免费视频| 不卡的av电影| 少妇精品一区二区| 久久一留热品黄| 亚洲午夜久久久久久久久红桃| 99热99精品| 波多野结衣 在线| 国产欧美一区二区精品忘忧草| 男人操女人动态图| 国产精品视频线看| 三级黄色在线观看| 一区二区免费在线| 国产大片中文字幕| 天天色综合成人网| 天天干天天干天天| 欧洲精品一区二区| 国产精品毛片久久久久久久av | 久久精品一二三四| 成人h动漫精品| 99久久久久久久久久| 国产视频视频一区| 黄色裸体一级片| 亚洲精品国产精品乱码不99 | 欧美三级电影在线观看| 91九色蝌蚪91por成人| 欧美日韩精品三区| 精品久久久无码中文字幕| 亚洲国产精久久久久久久| 巨骚激情综合| 久久精品国产91精品亚洲| 福利在线导航136| 欧美在线中文字幕| 四虎永久精品在线| 国产精品久久久久久免费观看| 亚洲女娇小黑人粗硬| 亚洲精品成人三区| 欧美性久久久| 国内自拍视频一区| 国产乱码精品一品二品| 醉酒壮男gay强迫野外xx| 中文成人av在线| 国产无遮挡又黄又爽又色| 欧美性xxxxxx少妇| 国产丝袜在线视频| 亚洲精品之草原avav久久| 国产在线高潮| 欧美在线一区二区视频| 成人短视频软件网站大全app| 国产精品一区二区三区观看| 波多野结衣在线观看一区二区| 亚洲色婷婷久久精品av蜜桃| 亚洲一区国产| 99中文字幕在线| 久久奇米777| 九九视频免费看| 欧美日韩视频一区二区| 婷婷在线观看视频| 久久视频在线播放| 成人在线免费在线观看| 日韩成人在线观看视频| 日本在线免费观看一区| 国产一区观看| 日本一二区免费| 久久精品一区二区| 日韩av无码中文字幕| 欧美一区午夜视频在线观看 | y97精品国产97久久久久久| 高清精品在线| 91在线短视频| 亚洲成人免费| 天天干天天玩天天操| 久久综合色之久久综合| 国产污片在线观看| 欧美一区二区日韩| 黄色网在线看| 国产精品入口夜色视频大尺度| 日韩aaa久久蜜桃av| 亚洲熟妇无码av在线播放| 韩国v欧美v亚洲v日本v| 久久久免费看片| 色综合夜色一区| 少妇又色又爽又黄的视频| 欧美日本高清一区| 99热这里有精品| 正义之心1992免费观看全集完整版| 噜噜噜在线观看免费视频日韩 | 国产成人精品无码播放| 成人国产亚洲欧美成人综合网| 一区二区在线观看免费视频| 9191成人精品久久| 免费大片在线观看www| 国产欧美日韩专区发布| 欧美色图国产精品| 久久久久久久久久久久91| 国产女人18毛片水真多成人如厕 | 精品国产美女在线| 999国产精品亚洲77777| 日产精品一线二线三线芒果| 久热精品在线| 免费看黄色的视频| 日韩欧美精品网站| 欧洲一区av| 国产成人一区二区三区电影| 国产va免费精品观看精品视频| 已婚少妇美妙人妻系列| 中文字幕久久午夜不卡| 亚洲天堂一二三| 欧美大肥婆大肥bbbbb| 亚洲性视频在线| 男人插女人视频在线观看| 99久久夜色精品国产网站| 毛片毛片女人毛片毛片| 一本色道久久88综合亚洲精品ⅰ| 国产资源一区| 麻豆一区二区三区在线观看| 成人午夜碰碰视频| 国产成人在线观看网站| 国产一区二区三区18| 亚洲日本中文| 青草青青在线视频| 久久理论电影网| 一级黄色片网站| 波多野结衣一区| 日本888xxxx| 亚洲精品日日夜夜| 天天干,天天操,天天射| 青青草99啪国产免费| 欧美限制电影| 一级黄色免费毛片| 五月婷婷激情综合网| 国产福利小视频在线| 91中文字幕在线观看| 99热免费精品| 中文字幕欧美激情极品| 欧美一区二区三区白人| 麻豆免费在线| 亚洲精品人成| 成人黄页在线观看| 中国女人真人一级毛片| 欧美寡妇偷汉性猛交| 精品久久不卡| 亚洲一区二区三区黄色| 欧美色涩在线第一页| 秋霞在线视频| 性刺激综合网| 成人黄色a**站在线观看| 亚洲天堂网在线观看视频| 97精品久久久| 91精品国产视频| 97伦伦午夜电影理伦片| 日韩一区二区在线看| 成人看片在线观看| 精品少妇人欧美激情在线观看| 欧美激情一区二区三区蜜桃视频| 免费观看国产视频|