vue3 宇宙之 update(看得見的思考)

2020-12-04 開課吧

本篇還是以看得見的思考來分析整個 update 過程,後續還會有總結的文章。

基本的流程是:先用看的見的思考來看源碼,然後總結。

scopeId 都有啥用?

初始化邏輯搞定之後,接著就是看看更新邏輯了。

更新邏輯著可是核心中的核心,這個搞懂之後,在去給 vue 提 pr 那可就輕鬆多了。

好了, 廢話不多說了 先看源碼。

什麼時候會觸發 update 的邏輯?

首先第一個問題,我們需要考慮什麼時候會觸發 update 的邏輯。

先上應用層的代碼:

可以看這個組件,當我們點擊 button 的時候 視圖一定會刷新。

那麼在源碼裡面的流程是怎麼樣的呢?

還記得我們的 setupRenderEffect 邏輯嘛?

還是貼代碼再回顧一下吧:

重點來了,注意這裡的 effect。

我們是在 effect 裡面調用的 render 函數,

而當我們調用 render 函數的話,肯定會觸發響應式對象的 get ,這其實是關於 reactivity 的核心邏輯,如何收集依賴和如何觸發依賴的。

這裡我們暫時知道當我們調用 render 函數之後,會觸發依賴收集,收集的就是當前用 effect 包裹的這個 function,後面當我們的響應式數據變動的時候回再次調用這個 function。

比如上面使用層的代碼,點擊按鈕的時候 count 變了。

當 count 變了之後就會觸發依賴,也就調用了我們的這個 function,這也就是 update 邏輯的入口,

這個入口整明白了就和初始化的邏輯結合在一起了。

接著往下看:

現在可以直接掛住 else 裡面的邏輯了。

我先整理一下代碼:

這裡有個疑惑點是 Instance.next 是個什麼鬼,

先去查查注釋:

好吧,看著注釋也沒有太看明白。

先過,回頭再來看。

不過猜測一下的話,第一次 update 的時候這個 next 應該是個 null ,

那麼我先把涉及到處理 next 的邏輯先去掉。

咦,我發現在這裡給 next 賦值了。next 等於當前的 vnode 。

調用 renderComponentRoot

然後調用:

renderComponentRoot(instance),

簡化邏輯:

我們看看到底都做了啥:

再次調用 render 函數得到新的 vnode ,這裡命名為 result;

繼承之前 vnode 的父級 scopeId ?這裡的 scopeId 都有啥用呢?(記錄一下);

繼承 directives;

繼承 transition data;

繼承 ref。

稍微分析分析,其實呢,這裡就是再次調用 render 函數,然後返回出去,別的雜七雜八的事先不管。

再次回到 setupRenderEffect 繼續往下看:

這也是個關鍵邏輯,做數據的更替了:

把之前的 vnode 賦值給 prevTree,把現在的 vnode 賦值給 instance.subTree,

接著還需要更新一下 el(實際渲染出來的 element),

接著調用一些 hook:

beforeUpdate hook;

onVnodeBeforeUpdate.

接著就是重點啦,再次調用 patch:

只不過和我們初始化的時候對比,現在的 n1 是有值的了。

我們先把後面的邏輯看完,然後在看是如何在 patch 裡面對比兩個節點的:

因為 patch 完了之後有可能會生成一個新的 el ,所以需要把新的 el 賦值給新的 vnode 上:

這裡還是 hook 的調用:

updated hook;

onVnodeUpdated.

好,接著我們就進入重頭戲。

updateComponent

因為會再次調用 patch ,然後會進行 component 類型的處理,這裡當然是調用 updateComponent 啦,所以我們直接看著邏輯。

簡化邏輯:

這裡有幾個比較重要的邏輯函數:

shouldUpdateComponent 判斷到底需不需要更新;

如果需要更新的話,

調用 updateComponentPreRender ,

或者

invalidateJob 和 instance.update();

不需要更新的話直接把之前的屬性拿過來即可。

我們這裡主要分析的是 happy path ,所以只會執行到 else 裡面的邏輯,也就是調用 instance.update()。

而調用 update 的話,就會再次執行一遍 setupRenderEffect。

我們等等再來看,

先看下 shouldUpdateComponent.

shouldUpdateComponent

其實這個函數的回答的問題是,什麼情況下需要更新組件呢?

先簡化一下代碼:

逐個來分析的話:

如果有 dirs 和 transition 的話,會更新;

接著是判斷了 patchFlag ,這個 flag 也是個很值得一說的點,它是在編譯階段生成的,不同的模板類型會生成不一樣的值,這個可以單獨寫個專題來分析,暫時我們先知道有這個 flag 即可。

大概有這麼幾種情況都需要更新:

PatchFlags.DYNAMIC_SLOTS;

PatchFlags.FULL_PROPS

這種情況的話還會對比一下之前的 props 和現在的 props 有啥不一樣的,發現只要有一個 prop 不一樣就會更新;

PatchFlags.PROPS 這種情況是檢測動態的 props ,這裡主要要關注的邏輯點是 nextVNode.dynamicProps 是什麼時候給賦值的;

接著就是檢測如果有 chilren 的話,那麼也需要更新。以上分析的暫時也只是個簡單的分析,具體的情況到時候在具體的分析,在回顧一下我們的目標,是了解 update 的流程,暫時先不太關注於細節。

好,最後的結論是對比一下,發現需要更新的話 就返回 true。

這個還思考了一個問題,就是為什麼不需要對比 chilren 呢?

應該是因為 component 就是個虛擬的箱子,假如箱子的表面行為都有變動,那麼再繼續深入,下面要關注的點就是如何觸發 updateElement 的。

接下來的邏輯應該是進入了這個分支:

我們在重新讀一下 update ,這裡和第一次進入 update 時有一點不同,就是 vnode.next 是有值的。(我們在第一次執行 update 的時候給 vnode 賦值的,還記得嗎)

哈哈,暫時發現一個有趣的點,默認的代碼第一次讀的時候感覺好難好複雜,但是你多看它幾遍的話,也就那麼回事,所以以後看到複雜的代碼你就多看它幾遍哈哈。

因為現在 next 是有值的,所以應該會進入到 updateComponentPreRender 函數內。

updateComponentPreRender

簡化邏輯:

就是更新了 props 和 slots ,這裡細節咱就先不看了。

再繼續往下看,我們暫時的問題是怎麼更新到 component 內部的 element 的呢?

啊哦,並沒有發現多餘的線索。

看來我需要找個例子來 debug 一下。

這裡我選擇的策略是先從最簡單的邏輯看起來:

這種情況是當前的這個 div (element) 的 id 是動態的,然後 8 代表的是 PatchFlags.PROPS 在後面的數組["id"] 裡面是標記著動態的 prop 是什麼,這裡當然就是 id 了。

有一點要說明的是,我們這裡是直接寫死的,如果是利用 template 來寫的話,它會自動生成。

好,按照這個思路 我看看它是如何處理 component 類型的。

在這種情況下,Parent 裡面是嵌套了一個 Child 組件,然後我們是在 parent 的 render 函數內傳給 child 的 props ,也就是說當 cId 變化後,它應該是先影響到 Child 。

接著我們來分析一下它整個 runtime 的所有邏輯流程:

cId.value ++ 的時候觸發 Parent 的 update 邏輯;

然後再次調用 Parent 的 render 函數,獲取到 subTree;

接著會觸發 patch ,著時候的參數就是新得到的 subTree ,也就是 createVNode(Child);

因為這個 vnode 是 Child ,類型是 component 所以會走 processComponent 邏輯;

因為 n1 是肯定有值的,所以走 updateComponent 邏輯;

在接下來會觸發

shouldUpdateComponent 邏輯,比對兩個 vnode ,看看是否需要更新,這裡是肯定需要更新的;

然後又觸發了 instance.update(),注意一下這裡的 instance 可是 Child;

好,我們又一次來到了 instance.update 內,這時候會再次調用 Child.render(),也可以說現在拆箱 Child;

拆箱 Child 得到的 vnode 就是 element p 了;

接著用 p 的 vnode 來調用 patch;

會調用到 patchElement ,繼而對比 element。

至此,對於上面的問題,從 component 是如何調用更新到內部的 element 的,就有了答案。

當然我們到現在為止只是分析了兩個最簡單的更新:組件的更新、element 的更新。

接著把整個流程整理一下:

修改響應式的值,觸發 effect 的回調函數(觸發依賴);

再次調用 render 函數,獲取最新的 vnode 值;

把新的 vnode 和舊的 vnode ,交給 patch;

patch 來基於 vnode 的類型進行處理具體的 update 邏輯;

如果是 component 類型的話,會做一個 updateComponent() 的處理,檢測是否可更新,如果可以更新的話會再次調用 update;

如果是 element 類型的話,會調用 patchElement 來檢測更新;

接著就是遞歸的調用當前組件的 render,獲取到最新的 subTree(vnode);

重複上面的過程。

我們稍微隱喻一下,如果是 component 類型的話,我們就需要檢測要不要開箱,

當需要開箱的話,再處理箱子裡面的 element 或者 component ,

如果是 component 那麼就重複上面的過程。應該是遞歸的向下查,截止點就是當前的 component 能不能開箱。

好,這個流程整理完了,怎麼對比細節,我們先不管,先把整個流程在 mini-vue 裡面實現一遍,看看有沒有邏輯落下。

至此整個 update 的流程就都已經分析完了,剩下的就是針對細節來分析了。

後面的策略是基於特定的場景來分析對應的 patch 邏輯,不然的話,邏輯太多,容易在細節中迷路。

相關焦點

  • vue3.0手機聊天室|Vue3+Vant3仿微信界面
    項目簡介Vue3Chat是基於vue3.x+vuex4+vue-router4全家桶技術和vant3.x構建開發的一款mobile仿微信界面聊天實例。實現了發送圖文消息、圖片|視頻預覽、位置查看、紅包|朋友圈等功能。
  • Vue全家桶之什麼是Vuex
    Vue全家桶之Vue2.X和Vue3.X腳手架創建項目的不同方式》或者在我的主頁查看以下項目目錄是Vue cli3.x創建的由於在創建項目時就安裝了Vuex所以目錄中以及創建好了store文件夾下的index.js也可以手動安裝 輸入命令 npm i vuex -s ,然後在項目的根目錄下新增一個store
  • Vue 3 的最新進展
    因此原本計劃2020年上半年發布 Vue 3,但考慮到目前的進度,不得不進行調整。現在的計劃是7月中旬發布 RC 版本,8月初正式發布 3.0 版本。Vue 3 主要部分的進展Vue 3 CoreVue 3 core 已經處於 beta 階段兩個多月,目前已合併所有計劃中的重大更改 RFC,並且在正式發布之前不會進行進一步的重大變更。
  • 什麼是MVVM,MVC和MVVM的區別,MVVM框架VUE實現原理
    比如vue,通過數據的雙向綁定,極大了提高了開發效率。3. MVVM框架:VUE的介紹Vue就是基於MVVM模式實現的一套框架,在vue中:Model:指的是js中的數據,如對象,數組等等。1.如果你已經有一個現成的服務端應用,你可以將vue 作為該應用的一部分嵌入其中,帶來更加豐富的交互體驗;2.如果你希望將更多業務邏輯放到前端來實現,那麼VUE的核心庫及其生態系統也可以滿足你的各式需求(core+vuex+vue-route)。
  • 什麼是VUE?VUE與JS的對比
    我們需要監視屬性和UI元素的變化 3. 我們需要讓所有綁定的對象和元素都能感知到變化1.1.1. vue與js的對比1.1.1.1. js的實現(了解)<!描述(vue的發展史)1.2.2.
  • 深入淺出 Vue 響應式原理!
    但有些數組操作Vue時攔截不到的,當然也就沒辦法響應,比如:obj.length-- // 不支持數組的長度變化obj[0]=1// 修改數組中第一個元素,也無法偵測數組的變化ES6提供了元編程的能力,所以有能力攔截,Vue3.0可能會用ES6中Proxy 作為實現數據代理的主要方式。
  • vue高級進階系列——用typescript玩轉vue和vuex
    本文轉載自【微信公眾號:趣談前端,ID:beautifulFront】經微信公眾號授權轉載,如需轉載與原文作者聯繫用過vue的朋友大概對vuex也不陌生,vuex的官方解釋是專為 Vue.js 應用程式開發的狀態管理模式。它採用集中式存儲管理應用的所有組件的狀態,並以相應的規則保證狀態以一種可預測的方式發生變化。
  • Vue.js深入學習
    vue.jsv-cloak:解決網速慢閃爍問題 ,不會替換掉標籤裡面的內容v-text:會替換掉標籤裡面的內容:原樣輸出v-html:會解析標籤v-bind:綁定屬性的指令,縮寫String.prototype.padEnd(2,'0');鍵盤修飾符:@keyup.enter="add"@keyup.113="add"自定義鍵盤碼: Vue.config.keyCodes.f2=113;全局指令:1.Vue中所有的指令都以v-開頭2.參數1,指令的名稱,定義的時候,指令的前面不需要加V-前綴,用的時候才加3.
  • 5分鐘帶你入門vuex(vue狀態管理)
    如果你之前使用過vue.js,你一定知道在vue中各個組件之間傳值的痛苦,在vue中我們可以使用vuex來保存我們需要管理的狀態值,值一旦被修改,所有引用該值的地方就會自動更新,那麼接下來我們就來學習一下vuex是如何修改狀態值的:我們新建一個vue項目(這裡由於我們是講解vuex,所以對於vue項目的創建我們不會講解太詳細);在命令行輸入 vue init
  • Vue+Axios+Vuex+Vux+Vue-Router全家桶搭建前端框架
    開發成本也就高了;二、再來看看React,出身名門FaceBook,社區比較多,而且還可以跨平臺運行,React-Native可以實現一次編寫,安卓、蘋果、web通殺;但是學習也需要一段時間,而且後期維護也不容易;其他的我們就不一一介紹了,最後來看看今天的重頭戲Vue;三、Vue,值得我們驕傲的是,國人發明的,一款漸進式框架,能與現有項目無縫融合,也就是說現有項目可以逐步更新替換使用vue
  • slot vue 具名專題及常見問題 - CSDN
    vue的slot1、什麼是slotslot是插槽,插即是可以插入,槽就是坑,即是可以再代碼中插入如果子組件模板中不包含插口,那麼父組件的內容將會被丟棄。當子組件模板中有一個麼有屬性的插槽時,父組件傳入的整個內容片段,將插入到插槽所在的dom的位置,並替換掉插槽標籤本身。
  • 官方Element Plus for Vue 3.0 Beta 版本今天發布了!
    今天,Element Plus for Vue 3.0 Beta版本正式發布了!對,就是那個被外界傳言不再維護的Element UI!官方團隊幾乎重寫了每一行 代碼,用最Vue 3的方式呈現了最完美的Element,主要有:使用TypeScript開發,提供完整的類型定義文件使用Vue 3.0 Composition API
  • vue-mapvgl v0.0.10 發布,基於 Vue 2.0 和百度地圖的地圖組件
    vue-mapvgl v0.0.10 已經發布,此版本更新內容包括:vue-mapvgl是一套基於Vue 2.0和百度地圖mapvgl的地圖組件。
  • vue-element-admin 4.0.0 正式版發布
    重大改變 基於 vue-cli@3進行構建 調整了項目的目錄結構 mock 文件移至根目錄下 layout 從 views 文件夾下移至 src 下 使用了最新的 eslint-plugin-vue
  • 如何在vue項目中使用sass並設置元素樣式
    GitnpmHBuilder瀏覽器技術vueelementJavaScriptsassCSS3 在創建vue框架項目時,可以使用CSS3定義元素樣式;還可以使用sass動態變量定義元素樣式那麼,如何在vue項目中使用sass?
  • 如何創建vue項目並使用element框架中的el-select
    1、在電腦硬碟上,找到一個位置,新建一個文件夾wmn;滑鼠右鍵選擇Git Bash Here,並在Git窗口輸入cnpm install --global vue-cli命令:cnpm install --global vue-cli
  • a標籤 href vue專題及常見問題 - CSDN
    vue.js 實現a標籤href裡添加參數原始碼列表可以正常顯示,但是連接沒有實現對items.orderNo的值轉化,最中解決方法
  • 如何寫一個vue組件專題及常見問題 - CSDN
    如何使用vue寫一個組件庫新建vue項目使用vue-cli初始化一個項目:vue init webpack VueComponentcd VueComponentnpm installnpm run dev以上就新建好了一個vue項目項目目錄首先,定義好目錄,經過觀察大多數的組件庫,基本是這樣的目錄
  • 供我們選擇的 Vue 組件庫還有很多!
    這裡放幾個知乎連結:element-ui 是不是不維護了如果 ElementUI 不維護了,也不再支持 Vue 3了我們該怎麼辦呢?仔細翻閱上述知乎回答,有些比較閒的作者已經 fork 了 ElementUI 的源碼,自己改造適配 Vue3,在此我只能說兩個字:敬佩。
  • ref vue 獲取文本專題及常見問題 - CSDN
    ref的官網介紹https://cn.vuejs.org/v2/api/#ref需求在普通的js操作中,一般都是直接操作dom元素,但是對於Vue.js框架來說,一般是不允許直接操作dom元素的。那麼其實Vue.js框架提供了ref獲取dom元素,以及組件引用。