源碼組織方式
提升代碼可維護(hù)性嚷缭,源碼采用 TypeScript 重寫
使用 Monorepo 管理項(xiàng)目結(jié)構(gòu)厦取,將獨(dú)立模塊提取到不同的包中砂蔽,每個(gè)模塊劃分明確尔苦,模塊依賴關(guān)系也很明確,并且每個(gè)功能模塊都可以單獨(dú)測試提陶、發(fā)布并使用
compiler
開頭的包都是和編譯相關(guān)的代碼烫沙,compiler-core
是和平臺無關(guān)的編譯器,compiler-dom
是瀏覽器平臺下的編譯器隙笆,依賴compiler-core
锌蓄;compiler-sfc
(single file component)單文件組件,用于編譯單文件組件撑柔,依賴compiler-core
和compiler-dom
瘸爽;compiler-ssr
是和服務(wù)端渲染相關(guān)的編譯器,依賴compiler-dom
reactivity
是數(shù)據(jù)響應(yīng)式系統(tǒng)铅忿,可單獨(dú)使用
runtime
開發(fā)的包都是運(yùn)行時(shí)代碼剪决,runtime-core
是和平臺無關(guān)的運(yùn)行時(shí),runtime-dom
是針對瀏覽器的運(yùn)行時(shí)檀训,處理原生 DOM API柑潦、事件等;runtime-test
是為測試而編寫的輕量的運(yùn)行時(shí)峻凫,渲染出的 DOM 樹是一個(gè) js 對象渗鬼,所以這個(gè)運(yùn)行時(shí)可以運(yùn)行在所有的 js 環(huán)境里,用它來測試渲染是否正確蔚晨,還可以用于序列化 DOM乍钻、觸發(fā) DOM 事件以及記錄某次更新中的 DOM 操作
server-renderer
是服務(wù)端渲染
shared
是 vue 內(nèi)部使用的一些公共 API
size-check
是私有包,不會發(fā)布到 npm铭腕,用于在 tree-shaking 后檢查包的大小
template-explorer
是在瀏覽器里運(yùn)行的實(shí)時(shí)編譯組件银择,會輸出 render 函數(shù)
vue
構(gòu)建完整版 vue,依賴于compiler
和runtime
Vue.js3.0 不同構(gòu)建版本
構(gòu)建不同版本累舷,用于不同的場合浩考,和 vue2.x 不同的是,不再構(gòu)建 umd 模塊方式被盈,umd 模塊方式會讓代碼更加冗余
-
packages/vue 存在所有構(gòu)建版本
-
說明
版本 名稱 說明 cjs(commonJS 模塊化方式析孽, vue.cjs.js 開發(fā)版,代碼未被壓縮 完整版 vue只怎,包含運(yùn)行時(shí)和編譯器) vue.cjs.prod.js 生產(chǎn)版本袜瞬,代碼被壓縮 global(全局,這 4 個(gè)文件都可以在瀏覽器中 vue.global.js 完整版身堡,開發(fā)版 通過 script 的方式引入邓尤,增加全局 vue 對象) vue.global.prod.js 完整版,生產(chǎn)版 vue.runtime.global.js 運(yùn)行時(shí)版本,開發(fā)版 vue.runtime.global.props.js 運(yùn)行時(shí)版本汞扎,生產(chǎn)版 browser(esModule 模塊化方式季稳,在瀏覽器中 vue.esm-browser.js 完整版,開發(fā)版 通過 type="module"的方式來導(dǎo)入) esm-browser.prod.js 完整版澈魄,生產(chǎn)版 vue.runtime.esm-browser.js 運(yùn)行時(shí)版本景鼠,開發(fā)版 vue.runtime.esm-browser.prod.js 運(yùn)行時(shí)版本,生產(chǎn)版 bundler(需要配合打包工具使用痹扇,使用 es Module vue.esm-bundler.js 完整版铛漓,還導(dǎo)入 runtime-compiler 方式,內(nèi)部通過 import 導(dǎo)入 runtime-core) vue.runtime.esm-bundler.js 運(yùn)行時(shí)鲫构,通過腳手架創(chuàng)建項(xiàng)目默認(rèn)使用此版本
Composition API
設(shè)計(jì)動(dòng)機(jī)
-
Options API
包含一個(gè)描述對象組件選項(xiàng)(data票渠、methods、props 等)的對象
Options API 開發(fā)負(fù)責(zé)組件芬迄,同一個(gè)功能邏輯的代碼被拆分到不同選項(xiàng)中
export default { data() { return { position: { x: 0, y: 0, }, } }, created() { window.addEventListener(' mousemove ', this.handle) }, destroyed() { window.removeEventListener('mousemove ', this.handle) }, methods: { handle(e) { this.position.x = e.pagexthis.position.y = e.pageY }, }, }
-
Composition API
Vue.js3.0 中新增的一組 API
一組基于函數(shù)的 API
可以更靈活的組織組件的邏輯
解決超大組件時(shí),使用 Options API 不好拆分和重用問題
// CompositionAPI import { reactive, onMounted, onUnmounted } from 'vue' function userMousePosition() { const position = reactive({ x: 0, y: 0, }) const update = (e) => { position.x = e.pageX position.y = e.pageY } onMounted(() => { window.addEventListener('mousemove', update) }) onUnmounted(() => { window.removeEventListener('mousemove', update) }) return position } export default { setup() { const position = useMousePosition() return { position, } }, }
同一色塊代表同一功能昂秃,Options API 中相同的代碼被拆分在不同位置禀梳,不方便提取重用代碼
Composition API 同一功能代碼不需要拆分,有利于代碼重用和維護(hù)
-
Composition Api vs Options Api
- 在邏輯組織和邏輯復(fù)用方面肠骆,
Composition API
是優(yōu)于Options API
- 因?yàn)?code>Composition API幾乎是函數(shù)算途,會有更好的類型推斷。
-
Composition API
對tree-shaking
友好蚀腿,代碼也更容易壓縮 -
Composition API
中見不到this
的使用嘴瓤,減少了this
指向不明的情況 - 如果是小型組件,可以繼續(xù)使用
Options API
莉钙,也是十分友好的
- 在邏輯組織和邏輯復(fù)用方面肠骆,
性能提升
-
響應(yīng)式系統(tǒng)升級
Vue.js2.x 中響應(yīng)式系統(tǒng)核心是 defineProperty廓脆,初始化時(shí)遍歷所有 data 中的成員,通過 defineProperty 將對象屬性轉(zhuǎn)換為 getter 和 setter磁玉,如何 data 中的對象又是對象的話停忿,需要遞歸處理每一個(gè)子對象屬性
Vue.js3.0 中使用 Proxy 對象重寫響應(yīng)式系統(tǒng),可以攔截屬性的訪問蚊伞、賦值席赂、刪除等操作
Proxy 好處:
- 可以監(jiān)聽動(dòng)態(tài)新增屬性,vue2.x 需要使用$set
- 可以監(jiān)聽刪除的屬性时迫,vue2.x 監(jiān)聽不到
- 可以監(jiān)聽數(shù)組的索引和 length 屬性颅停,vue2.x 監(jiān)聽不到
-
編譯優(yōu)化
對編譯器進(jìn)行優(yōu)化,重寫虛擬 DOM掠拳,首次渲染和 update 性能有了大幅度提升癞揉,這也是Vue3性能能夠得到提升的重要原因之一
<template> <div id="app"> <div> static root <div>static node</div> </div> <div>static node</div> <div>static node</div> <div>{{ count }}</div> <button @click="handler">button</button> </div> </template>
Vue.js2.x 在構(gòu)建過程中需要先編譯為 render 函數(shù),在編譯時(shí)通過標(biāo)記靜態(tài)根節(jié)點(diǎn),優(yōu)化 diff 過程(但是依然需要執(zhí)行 diff 操作)烧董,當(dāng)組件發(fā)生變化時(shí)毁靶,會通知 watcher,觸發(fā) watcher 的 update 方法逊移,最終執(zhí)行虛擬 DOM 的 patch 操作预吆,遍歷所有虛擬節(jié)點(diǎn)找到差異,然后更新到真實(shí) DOM 上胳泉;diff 過程中會比較整個(gè)虛擬 DOM拐叉,先對比新舊的 div,以及它的屬性扇商,再去對比內(nèi)部子節(jié)點(diǎn)凤瘦;
Vue.js2.x 中渲染的最小單位是組件
Vue.js3.0 中標(biāo)記和提升所有靜態(tài)根節(jié)點(diǎn),diff 時(shí)只需要對比動(dòng)態(tài)節(jié)點(diǎn)內(nèi)容
-
Fragments 片段案铺,模板中不需要在創(chuàng)建唯一的根節(jié)點(diǎn)蔬芥,需要升級 vetur 插件,查看Vue 3 Template Explorer
-
首先使用_createBlock
給根 div 創(chuàng)建 block控汉,是樹結(jié)構(gòu)笔诵,然后通過_createVNode
創(chuàng)建子節(jié)點(diǎn),相當(dāng)于h
函數(shù)姑子,當(dāng)刪除根節(jié)點(diǎn)時(shí)乎婿,會創(chuàng)建_Fragment
片段
-
靜態(tài)提升
打開
hoistStatic
靜態(tài)提升選項(xiàng),可以看到_createBlock
中的靜態(tài)節(jié)點(diǎn)都被提升到 render 函數(shù)外邊街佑,這些節(jié)點(diǎn)只有初始化時(shí)被創(chuàng)建一次谢翎,再次調(diào)用 render 時(shí)不會在被創(chuàng)建
- Patch flag
可以看到在動(dòng)態(tài)節(jié)點(diǎn)<div>{{ count }}</div>
通過_createVNode
渲染后,最終會有數(shù)字1
沐旨,這就是 Patch flag森逮。作為一個(gè)標(biāo)記,將來在執(zhí)行 diff 時(shí)會檢查整個(gè)block
中帶 Patch flag 標(biāo)記的節(jié)點(diǎn)希俩,如果 Patch flag 值為1
吊宋,代表文本內(nèi)容時(shí)動(dòng)態(tài)綁定,所以只會比較文本內(nèi)容是否發(fā)生變化
此時(shí)在給當(dāng)前 div 綁定一個(gè) id 屬性颜武,可以看到 Patch flag 變?yōu)?code>9璃搜,代表當(dāng)前節(jié)點(diǎn)的文本和 PROPS 是動(dòng)態(tài)內(nèi)容,并且記錄動(dòng)態(tài)綁定的 PROPS 是 id鳞上,將來 diff 時(shí)只會檢查此節(jié)點(diǎn)的文本和 id 屬性是否發(fā)生變化这吻,從而提升 diff 性能
- 緩存事件處理函數(shù)
開啟緩存后,首次渲染時(shí)會生成新的函數(shù)篙议,并將函數(shù)緩存到_cache
對象中唾糯,將來再次調(diào)用 render 時(shí)怠硼,會從緩存中獲取
-
源碼體積優(yōu)化
Vue.js3.0 移除一些不常用 API,如:inline-template移怯、filter 等
Tree-shaking 支持更好香璃,因?yàn)?Tree-shaking 依賴 ES Module,也就是 ES6 的模塊化語法結(jié)構(gòu)
import
和export
舟误,通過編譯階段的靜態(tài)分析葡秒,找到?jīng)]有引入的模塊,在打包的時(shí)候直接過濾掉嵌溢,從而減少打包體積眯牧。Vue.js3.x 的內(nèi)置組件 keepAlive、Trasition 和一些內(nèi)置指令都是按需引入赖草,并且 Vue.js3.x 中的很多 API 都是支持 Tree-shaking学少,沒有使用是不會進(jìn)行打包的
Vite
學(xué)習(xí) Vite 前,先需要了解 ES Module
除 IE 外秧骑,現(xiàn)代瀏覽器都支持 ES Moduie
-
加載模塊通過在 script 標(biāo)簽中 type="module"即可
<script type="module" src="..."></script>
-
支持模塊的 script 默認(rèn)延遲加載
類似于 script 標(biāo)簽設(shè)置 defer
在文檔解析完成后版确,觸發(fā)DOMContentLoaded事件前執(zhí)行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app">Hello world!</div>
<script>
window.addEventListener('DOMContentLoaded', () => {
console.log('DOMContentLoaded')
})
</script>
<script type="module" src="./modules/index.js"></script>
</body>
</html>
// modules/index.js
import { forEach } from './utils.js'
const app = document.querySelector('#app')
console.log(app.innerHTML)
const arr = [1, 2, 3]
forEach(arr, (item) => {
console.log(item)
})
type="module"
方式引入時(shí)需要在服務(wù)器中運(yùn)行項(xiàng)目,在 vsCode 中安裝插件live-server
乎折,右鍵啟動(dòng)項(xiàng)目
打開瀏覽器控制臺阀坏,可以看到輸出結(jié)果如下所示,可以看到index.js
模塊在文檔解析完成后笆檀,觸發(fā) DOMContentLoaded 事件前執(zhí)行
Vite vs Vue-Cli
- Vite 在開發(fā)模式下不需要打包可以直接運(yùn)行,因?yàn)?vite 在開發(fā)模式下使用瀏覽器支持的 es Module 加載模塊盒至,通過
<script type="module"></script>
的方式加載代碼酗洒,提升開發(fā)效率;vite 會開啟測試服務(wù)器枷遂,攔截瀏覽器發(fā)送請求樱衷,對瀏覽器不識別的模塊進(jìn)行處理,比如當(dāng) import 單文件組件時(shí)酒唉,會先進(jìn)行編譯矩桂,把編譯的結(jié)果發(fā)送給瀏覽器 - Vue-Cli 開發(fā)模式下必須對項(xiàng)目打包才可以運(yùn)行
- Vite 在生成環(huán)境下使用 Rollup 打包,基于 ES Module 的方式打包痪伦,不再需要使用 babel 把 import 轉(zhuǎn)換為 require侄榴,因此打包體積會小于 webpack 體積
- Vue-Cli 使用 Webpack 打包
Vite 特點(diǎn)
- 快速冷啟動(dòng)(不需要打包)
- 按需編譯(代碼加載時(shí)才會進(jìn)行編譯)
- 模塊熱更新
使用 Vite 創(chuàng)建基于 vue3 項(xiàng)目
npm install create-vite-app -g
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
基于模板創(chuàng)建項(xiàng)目
npm init vite-app --template react
npm init vite-app --template preact
通過 create-vite-app 創(chuàng)建完項(xiàng)目之后,App.vue 會有 eslint 語法錯(cuò)誤网沾,原因是 Vetur 插件還沒有更新
解決:文件 --> 首選項(xiàng) --> 設(shè)置 --> 搜索 eslint-plugin-vue --> Vetur ? Validation: Template 取消勾選
通過 npm run dev 啟動(dòng)項(xiàng)目
開發(fā)環(huán)境下癞蚕,vite 開啟 web 服務(wù)器后,會劫持.vue 結(jié)尾的文件辉哥,將.vue 文件轉(zhuǎn)換為 js 文件桦山,并將響應(yīng)中的 content-type 設(shè)置為 application/javascript攒射,告訴瀏覽器是 js 腳本