源碼組織方式
提升代碼可維護(hù)性,源碼采用 TypeScript 重寫
使用 Monorepo 管理項目結(jié)構(gòu),將獨立模塊提取到不同的包中,每個模塊劃分明確,模塊依賴關(guān)系也很明確,并且每個功能模塊都可以單獨測試、發(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),可單獨使用
runtime開發(fā)的包都是運行時代碼,runtime-core是和平臺無關(guān)的運行時,runtime-dom是針對瀏覽器的運行時,處理原生 DOM API、事件等;runtime-test是為測試而編寫的輕量的運行時,渲染出的 DOM 樹是一個 js 對象,所以這個運行時可以運行在所有的 js 環(huán)境里,用它來測試渲染是否正確,還可以用于序列化 DOM、觸發(fā) DOM 事件以及記錄某次更新中的 DOM 操作
server-renderer是服務(wù)端渲染
shared是 vue 內(nèi)部使用的一些公共 API
size-check是私有包,不會發(fā)布到 npm,用于在 tree-shaking 后檢查包的大小
template-explorer是在瀏覽器里運行的實時編譯組件,會輸出 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)建版本
image-20210412193022815.png
-
說明
版本 名稱 說明 cjs(commonJS 模塊化方式, vue.cjs.js 開發(fā)版,代碼未被壓縮 完整版 vue,包含運行時和編譯器) vue.cjs.prod.js 生產(chǎn)版本,代碼被壓縮 global(全局,這 4 個文件都可以在瀏覽器中 vue.global.js 完整版,開發(fā)版 通過 script 的方式引入,增加全局 vue 對象) vue.global.prod.js 完整版,生產(chǎn)版 vue.runtime.global.js 運行時版本,開發(fā)版 vue.runtime.global.props.js 運行時版本,生產(chǎn)版 browser(esModule 模塊化方式,在瀏覽器中 vue.esm-browser.js 完整版,開發(fā)版 通過 type="module"的方式來導(dǎo)入) esm-browser.prod.js 完整版,生產(chǎn)版 vue.runtime.esm-browser.js 運行時版本,開發(fā)版 vue.runtime.esm-browser.prod.js 運行時版本,生產(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 運行時,通過腳手架創(chuàng)建項目默認(rèn)使用此版本
Composition API
設(shè)計動機(jī)
-
Options API
包含一個描述對象組件選項(data、methods、props 等)的對象
Options API 開發(fā)負(fù)責(zé)組件,同一個功能邏輯的代碼被拆分到不同選項中
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
可以更靈活的組織組件的邏輯
解決超大組件時,使用 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, } }, }image-20210412200549917.png
同一色塊代表同一功能,Options API 中相同的代碼被拆分在不同位置,不方便提取重用代碼
Composition API 同一功能代碼不需要拆分,有利于代碼重用和維護(hù)
-
Composition Api vs Options Api
- 在邏輯組織和邏輯復(fù)用方面,
Composition API是優(yōu)于Options API - 因為
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,初始化時遍歷所有 data 中的成員,通過 defineProperty 將對象屬性轉(zhuǎn)換為 getter 和 setter,如何 data 中的對象又是對象的話,需要遞歸處理每一個子對象屬性
Vue.js3.0 中使用 Proxy 對象重寫響應(yīng)式系統(tǒng),可以攔截屬性的訪問、賦值、刪除等操作
Proxy 好處:
- 可以監(jiān)聽動態(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ù),在編譯時通過標(biāo)記靜態(tài)根節(jié)點,優(yōu)化 diff 過程(但是依然需要執(zhí)行 diff 操作),當(dāng)組件發(fā)生變化時,會通知 watcher,觸發(fā) watcher 的 update 方法,最終執(zhí)行虛擬 DOM 的 patch 操作,遍歷所有虛擬節(jié)點找到差異,然后更新到真實 DOM 上;diff 過程中會比較整個虛擬 DOM,先對比新舊的 div,以及它的屬性,再去對比內(nèi)部子節(jié)點;
Vue.js2.x 中渲染的最小單位是組件
Vue.js3.0 中標(biāo)記和提升所有靜態(tài)根節(jié)點,diff 時只需要對比動態(tài)節(jié)點內(nèi)容
-
Fragments 片段,模板中不需要在創(chuàng)建唯一的根節(jié)點,需要升級 vetur 插件,查看Vue 3 Template Explorer
image-20210413082819879.png
-
首先使用_createBlock給根 div 創(chuàng)建 block,是樹結(jié)構(gòu),然后通過_createVNode創(chuàng)建子節(jié)點,相當(dāng)于h函數(shù),當(dāng)刪除根節(jié)點時,會創(chuàng)建_Fragment片段

-
靜態(tài)提升
打開
hoistStatic靜態(tài)提升選項,可以看到_createBlock中的靜態(tài)節(jié)點都被提升到 render 函數(shù)外邊,這些節(jié)點只有初始化時被創(chuàng)建一次,再次調(diào)用 render 時不會在被創(chuàng)建image-20210413083144478.png
- Patch flag

可以看到在動態(tài)節(jié)點<div>{{ count }}</div>通過_createVNode渲染后,最終會有數(shù)字1,這就是 Patch flag。作為一個標(biāo)記,將來在執(zhí)行 diff 時會檢查整個block中帶 Patch flag 標(biāo)記的節(jié)點,如果 Patch flag 值為1,代表文本內(nèi)容時動態(tài)綁定,所以只會比較文本內(nèi)容是否發(fā)生變化

此時在給當(dāng)前 div 綁定一個 id 屬性,可以看到 Patch flag 變?yōu)?code>9,代表當(dāng)前節(jié)點的文本和 PROPS 是動態(tài)內(nèi)容,并且記錄動態(tài)綁定的 PROPS 是 id,將來 diff 時只會檢查此節(jié)點的文本和 id 屬性是否發(fā)生變化,從而提升 diff 性能
- 緩存事件處理函數(shù)

開啟緩存后,首次渲染時會生成新的函數(shù),并將函數(shù)緩存到_cache對象中,將來再次調(diào)用 render 時,會從緩存中獲取
-
源碼體積優(yōu)化
Vue.js3.0 移除一些不常用 API,如:inline-template、filter 等
Tree-shaking 支持更好,因為 Tree-shaking 依賴 ES Module,也就是 ES6 的模塊化語法結(jié)構(gòu)
import和export,通過編譯階段的靜態(tài)分析,找到?jīng)]有引入的模塊,在打包的時候直接過濾掉,從而減少打包體積。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"方式引入時需要在服務(wù)器中運行項目,在 vsCode 中安裝插件live-server,右鍵啟動項目

打開瀏覽器控制臺,可以看到輸出結(jié)果如下所示,可以看到index.js模塊在文檔解析完成后,觸發(fā) DOMContentLoaded 事件前執(zhí)行

Vite vs Vue-Cli
- Vite 在開發(fā)模式下不需要打包可以直接運行,因為 vite 在開發(fā)模式下使用瀏覽器支持的 es Module 加載模塊,通過
<script type="module"></script>的方式加載代碼,提升開發(fā)效率;vite 會開啟測試服務(wù)器,攔截瀏覽器發(fā)送請求,對瀏覽器不識別的模塊進(jìn)行處理,比如當(dāng) import 單文件組件時,會先進(jìn)行編譯,把編譯的結(jié)果發(fā)送給瀏覽器 - Vue-Cli 開發(fā)模式下必須對項目打包才可以運行
- Vite 在生成環(huán)境下使用 Rollup 打包,基于 ES Module 的方式打包,不再需要使用 babel 把 import 轉(zhuǎn)換為 require,因此打包體積會小于 webpack 體積
- Vue-Cli 使用 Webpack 打包
Vite 特點
- 快速冷啟動(不需要打包)
- 按需編譯(代碼加載時才會進(jìn)行編譯)
- 模塊熱更新
使用 Vite 創(chuàng)建基于 vue3 項目
npm install create-vite-app -g
npm init vite-app <project-name>
cd <project-name>
npm install
npm run dev
基于模板創(chuàng)建項目
npm init vite-app --template react
npm init vite-app --template preact
通過 create-vite-app 創(chuàng)建完項目之后,App.vue 會有 eslint 語法錯誤,原因是 Vetur 插件還沒有更新

解決:文件 --> 首選項 --> 設(shè)置 --> 搜索 eslint-plugin-vue --> Vetur ? Validation: Template 取消勾選

通過 npm run dev 啟動項目
開發(fā)環(huán)境下,vite 開啟 web 服務(wù)器后,會劫持.vue 結(jié)尾的文件,將.vue 文件轉(zhuǎn)換為 js 文件,并將響應(yīng)中的 content-type 設(shè)置為 application/javascript,告訴瀏覽器是 js 腳本




