大概幾集下飯劇時(shí)間就能懂的Vue3原理
大概幾集下飯劇時(shí)間就能懂的VUE3原理
大家好,我是卡頌。
最近中午沒胃口,找來(lái)VUE源碼相關(guān)視頻來(lái)當(dāng)下飯劇。幾頓飯下去,人胖了,VUE也整明白了。
這篇文章為你帶來(lái)一份VUE3原理速成指南。
模塊劃分
如果我們用「VUE的模版語(yǔ)法」定義:
- <div>hello</div>
最終VUE會(huì)幫我們?cè)跒g覽器中渲染對(duì)應(yīng)的DOM節(jié)點(diǎn)。
這之間對(duì)這段節(jié)點(diǎn)的描述會(huì)經(jīng)歷4次變化,橫跨「編譯時(shí)」與「運(yùn)行時(shí)」:
「模版語(yǔ)法」在編譯時(shí)會(huì)被「編譯器」轉(zhuǎn)化為「render函數(shù)」,類似:
- render(h) {
- return h('div', 'hello');
- }
在運(yùn)行時(shí),「render函數(shù)」執(zhí)行后返回的「h函數(shù)的執(zhí)行結(jié)果」就是VNode(也就是虛擬DOM),類似:
- {
- tag: "div",
- children: [
- {
- text: "Hello"
- }
- ]
- }
最終,VUE根據(jù)VNode的信息,在瀏覽器渲染對(duì)應(yīng)DOM。
那么,是誰(shuí)在驅(qū)動(dòng)這一流程?
mount和patch
組件有兩種不同的渲染邏輯:「首次渲染」和「更新」。
「首次渲染」意味著從無(wú)到有,比如上文的VNode:
- {
- tag: "div",
- children: [
- {
- text: "Hello"
- }
- ]
- }
可能對(duì)應(yīng)如下DOM操作:
- const node = document.createElement(VNode.tag);
- node.textConent = 'Hello';
- contanerDOM.appendChild(node);
「更新」則需要對(duì)比更新前后VNode,對(duì)變化部分執(zhí)行DOM操作。
比如,以上VNode如果變?yōu)椋?/p>
- {
- tag: "div",
- children: [
- {
- // text改變
- text: "world"
- }
- ]
- }
則最終執(zhí)行:
- node.textContent = 'world';
VUE的「首次渲染」對(duì)應(yīng)mount模塊,「更新」對(duì)應(yīng)patch模塊。
所以,render函數(shù)執(zhí)行后返回VNode,根據(jù)情況不同,會(huì)走mount或patch的渲染邏輯:
如果想深入虛擬DOM相關(guān)知識(shí),推薦閱讀snabbdom[1]源碼。這是個(gè)優(yōu)秀的虛擬DOM庫(kù),VUE2的虛擬DOM部分就是fork這個(gè)庫(kù)改造的。
那么是誰(shuí)在什么時(shí)機(jī)調(diào)用了render函數(shù)呢?
響應(yīng)式更新
在VUE中,狀態(tài)變化會(huì)實(shí)時(shí)反映到視圖上,比如:
- <div @click="count++">{{count}}</div>
點(diǎn)擊div后:
- 觸發(fā)點(diǎn)擊事件,count變化
- count變化觸發(fā)回調(diào),回調(diào)中更新視圖
當(dāng)前我們已經(jīng)知道第二步是由于觸發(fā)了如下流程:
所以只需要建立count變化到執(zhí)行render函數(shù)的聯(lián)系即可。
具體來(lái)說(shuō),我們希望實(shí)現(xiàn)reactive及watchEffect:
- // 定義狀態(tài)
- const state = reactive({count: 0});
- // 監(jiān)聽狀態(tài)變化
- watchEffect(() => {
- console.log(state.count);
- })
- // 改變狀態(tài)
- state.count++;
reactive定義狀態(tài)。
watchEffect根據(jù)回調(diào)執(zhí)行的情況決定監(jiān)聽哪些狀態(tài)。
比如watchEffect回調(diào)執(zhí)行了console.log(state.count);,他就會(huì)監(jiān)聽state的變化。
當(dāng)執(zhí)行state.count++;,由于watchEffect監(jiān)聽了state的變化,則其回調(diào)會(huì)觸發(fā),打印state.count。
這就是Reactivity模塊。
VUE官方推出了VUE3響應(yīng)式原理[2]課程講解Reactivity的實(shí)現(xiàn),這是B站鏈接。如果經(jīng)濟(jì)允許,請(qǐng)支持正版[3]
當(dāng)實(shí)現(xiàn)了Reactivity模塊,我們就能將「組件狀態(tài)」與后續(xù)流程串聯(lián)起來(lái)。
剛才講過(guò),render函數(shù)是編譯器根據(jù)「模版語(yǔ)法」生成的。在面對(duì)帶狀態(tài)的模版語(yǔ)法時(shí),比如上文的count:
- <div @click="count++">{{count}}</div>
render函數(shù)內(nèi)的count是響應(yīng)式的(即:count實(shí)際是reactive({count: 0}))。
那么就能用watchEffect監(jiān)聽count的變化。
所以,在應(yīng)用初始化時(shí),會(huì)有類似邏輯:
- let isMounted = false;
- let oldVNode;
- watchEffect(() => {
- if (!isMounted) {
- // mount邏輯
- // 調(diào)用render函數(shù)
- oldVNode = component.render();
- // mount
- mount(oldVNode);
- } else {
- // patch邏輯
- // 調(diào)用render函數(shù)
- newVNode = component.render();
- patch(oldVNode, newVNode);
- oldVNode = newVNode;
- }
- })
其中component.render()(render函數(shù)的執(zhí)行)達(dá)到上文「監(jiān)聽狀態(tài)變化」的效果:
- // 監(jiān)聽狀態(tài)變化
- watchEffect(() => {
- console.log(state.count);
- })
所以,該組件內(nèi)任何狀態(tài)變化都會(huì)觸發(fā)watchEffect的執(zhí)行,watchEffect回調(diào)內(nèi)會(huì)觸發(fā)后續(xù)流程。
總結(jié)
VUE3按原理大體可以劃分為:
- mount
- patch
- 編譯器
- Reactivity
VUE官方推出了實(shí)現(xiàn)簡(jiǎn)易VUE3教程[4],感興趣的朋友可以去看看。如果有能力,記得去支持正版[5]哦。
參考資料
[1]snabbdom:
https://github.com/snabbdom/snabbdom
[2]VUE3響應(yīng)式原理:
https://www.bilibili.com/video/BV1SZ4y1x7a9/?spm_id_from=333.788.b_7265636f5f6c697374.6
[3]正版:
https://www.vuemastery.com/free-weekend/?gclid=Cj0KCQjwpreJBhDvARIsAF1_BU16x7gElbhGqGzZZ1geo5RzOqz_PuaJzBM41jHcAAC6CPwPSPvo8G8aAkdhEALw_wcB
[4]實(shí)現(xiàn)簡(jiǎn)易VUE3教程:
https://www.bilibili.com/video/BV1rC4y187Vw?p=10
[5]正版:
https://www.vuemastery.com/free-weekend/?gclid=Cj0KCQjwpreJBhDvARIsAF1_BU16x7gElbhGqGzZZ1geo5RzOqz_PuaJzBM41jHcAAC6CPwPSPvo8G8aAkdhEALw_wcB