vuejs 設計與實現 - 組件的實現原理

1.渲染組件

如果是組件則:vnode .type的值是一個對象。如下:

const vnode = {type: MyComponent,}

為了讓渲染器能處理組件類型的虛擬節點,我們還需要在patch函數中對組件類型的虛擬節點進行處理,如下:

function patch(n1, n2, container, anchor) {if(!n1 && n1.type !== n2.type) {unmount(n1)n1 = nill}const { type } = n2if (typeof type === 'string') {} else if (typeof type === 'object') {// 組件if (!n1) {// 掛載組件
+			mountComponent(n2, container,anchor )} else {// 更新組件
+			patchComponent(n1, n2, anchor)}}
}

一個組件必須包含一個渲染函數,即render函數,并且渲染函數的返回值應該是虛擬dom。如下:

const MyComponent = {name: 'MyComponent',render() {return {type: 'div',children: '我是文本'}}
}

有了基本的結構,渲染器就能完成組件的渲染。渲染器中真正完成組件的渲染的是mountComponent函數。實現如下:

function mountComponent(vnode, container, anchor) {// 通過vnode獲取組件的選項對象,即vnode.typeconst componentOptions = vnode.type// 獲取組件的渲染函數const { render } = componentOptions// 執行渲染函數,獲取組件的渲染函數內容,即render返回的虛擬domconst subTree = render()// 最后調用 patch 函數來掛載組件所描述的內容,即 subTreepatch(null, subTree, container, anchor)
}

2.組件狀態與自更新

為組件設計自身的狀態:data
我們用data函數來定義組件自身的狀態。

const MyComponent = {name: 'MyComponent',data() {return {foo: 'dd'}},render() {return {type: 'div',children: `foo 的值是: ${this.foo}` // 在渲染函數內使用組件狀態}}
}

我們約定用戶必須使用data函數來定義組件自身的狀態,同時可以在渲染函數中通過this訪問data函數返回的狀態數據:

function mountComponent(vnode, container, anchor) {// 通過vnode獲取組件的選項對象,即vnode.typeconst componentOptions = vnode.type// 獲取組件的渲染函數
+	const { render, data } = componentOptions+	//調用 data 函數得到原始數據,并調用 reactive 函數將其包裝為響應式數據
+  const state = reactive(data())// 執行渲染函數,獲取組件的渲染函數內容,即render返回的虛擬dom
+	// 調用 render 函數時,將其 this 設置為 state,從而 render 函數內部可以通過 this 訪問組件自身狀態數據
+	const subTree = render.call(state,state)// 最后調用 patch 函數來掛載組件所描述的內容,即 subTreepatch(null, subTree, container, anchor)
}

實現組件自身狀態的初始化需要兩個步驟:

    1. 通過組件的選項對象取得 data 函數并執行,然后調用 reactive 函數將 data 函數返回的狀態包裝為響應式數據;
    1. 在調用 render 函數時,將其 this 的指向設置為響應式數據 state,同時將 state 作為 render 函數的第一個參數傳遞。

當組件自身狀態發生變化時,我們需要有能力觸發組件更新,即 組件的自更新。為此,我們需要將整個渲染任務包裝到一個effect中,如下:

function mountComponent(vnode, container, anchor) {// 通過vnode獲取組件的選項對象,即vnode.typeconst componentOptions = vnode.type// 獲取組件的渲染函數const { render, data } = componentOptions//調用 data 函數得到原始數據,并調用 reactive 函數將其包裝為響應式數據const state = reactive(data())+	// 將組件的 render 函數調用包裝到 effect 內
+	effect(() => {// 執行渲染函數,獲取組件的渲染函數內容,即render返回的虛擬dom// 調用 render 函數時,將其 this 設置為 state,從而 render 函數內部可以通過 this 訪問組件自身狀態數據const subTree = render.call(state,state)// 最后調用 patch 函數來掛載組件所描述的內容,即 subTreepatch(null, subTree, container, anchor)
+	})}

將組件的 render 函數調用包裝到 effect 內,這樣一旦組件自身響應式數據發生變化,組件就會自動重新 執行渲染函數,從而完成更新。但是,由于effect的執行是同步的,因此放響應式數據發生變化時,與之關聯的副作用函數會同步執 行。
換句話說,如果多次修改響應式數據的值,將會導致渲染函數執 行多次,這實際上是沒有必要的。因此,我們需要設計一個機制,以 使得無論對響應式數據進行多少次修改,副作用函數都只會重新執行 一次。為此,我們需要實現一個調度器,當副作用函數需要重新執行 時,我們不會立即執行它,而是將它緩沖到一個微任務隊列中,等到 執行棧清空后,再將它從微任務隊列中取出并執行。有了緩存機制,我們就有機會對任務進行去重,從而避免多次執行副作用函數帶來的性能開銷。 具體實現如下:

// 任務緩存隊列,用一個 Set 數據結構來表示,這樣就可以自動對任務進行去重
const queue = new Set()// 一個標志,代表是否正在刷新任務隊列
let isFlushing = false// 創建一個立即 resolve 的 Promise 實例
const p = Promiser.resolve()// 調度器的主要函數,用來將一個任務添加到緩沖隊列中,并開始刷新隊列
function queueJob(job) {queue.add(job)// 如果還沒有開始刷新隊列,則刷新之if (!isFlushing) {isFlushing = truep.then(() => {try {// 執行任務隊列中的任務queue.forEach((job) => job())} finally{// 重置狀態isFlushing = falsequeue.clear = 0}})} 
}

上面是調度器的最小實現,本質上利用了微任務的異步執行機 制,實現對副作用函數的緩沖。其中 queueJob 函數是調度器最主要 的函數,用來將一個任務或副作用函數添加到緩沖隊列中,并開始刷 新隊列。有了 queueJob 函數之后,我們可以在創建渲染副作用時使 用它,

function mountComponent(vnode, container, anchor) {// 通過vnode獲取組件的選項對象,即vnode.typeconst componentOptions = vnode.type// 獲取組件的渲染函數const { render, data } = componentOptions//調用 data 函數得到原始數據,并調用 reactive 函數將其包裝為響應式數據const state = reactive(data())// 將組件的 render 函數調用包裝到 effect 內effect(() => {// 執行渲染函數,獲取組件的渲染函數內容,即render返回的虛擬dom// 調用 render 函數時,將其 this 設置為 state,從而 render 函數內部可以通過 this 訪問組件自身狀態數據const subTree = render.call(state,state)// 最后調用 patch 函數來掛載組件所描述的內容,即 subTreepatch(null, subTree, container, anchor)}, {// 指定該副作用函數的調度器為 queueJob 即可scheduler: queueJob})}

這樣,當響應式數據發生變化時,副作用函數不會立即同步執行,而是會被 queueJob 函數調度,最后在一個微任務中執行。

不過,上面這段代碼存在缺陷。可以看到,我們在 effect 函數內調用 patch 函數完成渲染時,第一個參數總是 null。這意味著,每次更新發生時都會進行全新的掛載,而不會打補丁,這是不正確的。正確的做法是:每次更新時,都拿新的 subTree 與上一次組件所渲染的 subTree 進行打補丁。為此,我們需要實現組件實例,用它來維護組件整個生命周期的狀態,這樣渲染器才能夠在正確的時機執行合適的操作。

3.組件實例與組件的生命周期

組件實例本質上是一個狀態集合(對象)。
引入組件實例

function mountComponent(vnode, container, anchor) {// 通過vnode獲取組件的選項對象,即vnode.typeconst componentOptions = vnode.type// 獲取組件的渲染函數const { render, data } = componentOptions//調用 data 函數得到原始數據,并調用 reactive 函數將其包裝為響應式數據const state = reactive(data())+	const instance = {
+		state, // 組件自身的狀態數據,即 data
+		isMounted: false, //  一個布爾值,用來表示組件是否已經被掛載,初始值為 false
+		subTree: null // 組件所渲染的內容,即子樹(subTree)
+	}// 將組件實例設置到 vnode 上,用于后續更新
+	vnode.component = instance// 將組件的 render 函數調用包裝到 effect 內effect(() => {// 執行渲染函數,獲取組件的渲染函數內容,即render返回的虛擬dom// 調用 render 函數時,將其 this 設置為 state,從而 render 函數內部可以通過 this 訪問組件自身狀態數據const subTree = render.call(state,state)// // 檢查組件是否已經被掛載
+		if (!isMounted) {// 初次掛載,調用 patch 函數第一個參數傳遞 null
+			patch(null, subTree, container, anchor)+			// 將組件實例的isMounted設置為true,這樣當更新發生時就不會再次進行掛載操作。而是執行更新
+			instance.isMounted  = true
+		} else {
+			// 當isMounted為true時,說明組件已經掛載了,只需要完成自更新即可
+			patch(instance.subTree,subTree, conatiner, anchor)
+		}// 最后調用 patch 函數來掛載組件所描述的內容,即 subTree
+		patch(null, subTree, container, anchor)
+     // 更新組件實例的子樹
+ 		instance.subTree = subTree}, {// 指定該副作用函數的調度器為 queueJob 即可scheduler: queueJob})}

在上面這段代碼中,我們使用一個對象來表示組件實例,該對象有三個屬性。

  • state:組件自身的狀態數據,即 data。
  • isMounted:一個布爾值,用來表示組件是否被掛載。
  • subTree:存儲組件的渲染函數返回的虛擬 DOM,即組件的子樹 (subTree)。

在上面的實現中,組件實例的 instance.isMounted 屬性可以 用來區分組件的掛載和更新。

function mountComponent(vnode, container, anchor) {// 通過vnode獲取組件的選項對象,即vnode.typeconst componentOptions = vnode.type// 從組件選項對象中取得組件的生命周期函數
+	const { render, data, beforeCreate, created, beforeMount,
mounted, beforeUpdate, updated } = componentOptions// 在這里調用beforeCreate鉤子beforeMount && beforeMount()const state = reactive(data())const instance = {state,isMounted: false,subTree: null}	vnode.component = instance// 在這里調用 created 鉤子created && created(state)effect(() => {const subTree = render.call(state, state)if (!instance.isMounted) {beforeMount && beforeMount.call(state)}}) 	}

4.props與組件的被動更新

5.setup函數的作用與實現

6.組件事件與emit的實現

7.插槽的工作原理與實現

8.注冊生命周期

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/36932.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/36932.shtml
英文地址,請注明出處:http://en.pswp.cn/news/36932.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

CentOS7.9 禁用22端口,使用其他端口替代

文章目錄 業務場景操作步驟修改sshd配置文件修改SELinux開放給ssh使用的端口修改防火墻,開放新端口重啟sshd生效 相關知識點介紹sshd服務SELinux服務firewall.service服務 業務場景 我們在某市實施交通信控平臺項目,我們申請了一臺服務器,用…

學習Vue:列表渲染(v-for)

在 Vue.js 中,實現動態列表的顯示是非常常見的需求。為了達到這個目的,Vue 提供了 v-for 指令,它允許您迭代一個數組或對象,將其元素渲染為列表。然而,在使用 v-for 時,key 屬性的設置也非常重要&#xff0…

微信小程序(原生)搜索功能實現

一、效果圖 二、代碼 wxml <van-searchvalue"{{ keyword }}"shape"round"background"#000"placeholder"請輸入關鍵詞"use-action-slotbind:change"onChange"bind:search"onSearch"bind:clear"onClear&q…

實踐-CNN卷積層

實踐-CNN卷積層 1 卷積層構造2 整體流程3 BatchNormalization效果4 參數對比5 測試效果 1 卷積層構造 2 整體流程 根據網絡結構來寫就可以了。 池化 拉平 訓練一個網絡需要2-3天的時間。用經典網絡來&#xff0c;一些細節沒有必要去扣。 損失函數&#xff1a; fit模型&…

運維監控學習筆記1

1、監控對象&#xff1a; 1、監控對象的理解&#xff1b;CPU是怎么工作的&#xff1b; 2、監控對象的指標&#xff1a;CPU使用率&#xff1b;上下文切換&#xff1b; 3、確定性能基準線&#xff1a;CPU負載多少才算高&#xff1b; 2、監控范圍&#xff1a; 1、硬件監控&#x…

線性掃描寄存器分配算法介紹

線性掃描寄存器分配 文章目錄 線性掃描寄存器分配1. 算法介紹2. 相關概念3. 算法的實現3.1 偽代碼3.2 圖示 參考文獻 論文地址&#xff1a; Linear Scan Register Allocation ? 我們描述了一種稱為線性掃描的快速全局寄存器分配的新算法。該算法不基于圖形著色&#xff0c;而…

echarts3d柱狀圖

//畫立方體三個面 const CubeLeft echarts.graphic.extendShape({shape: {x: 0,y: 0,width: 9.5, //柱狀圖寬zWidth: 4, //陰影折角寬zHeight: 3, //陰影折角高},buildPath: function (ctx, shape) {const api shape.api;const xAxisPoint api.coord([shape.xValue, 0]);con…

陪診小程序開發|陪診陪護小程序讓看病不再難

陪診小程序通過與醫療機構的合作&#xff0c;整合了醫療資源&#xff0c;讓用戶能夠更加方便地獲得專業醫療服務。用戶不再需要面對繁瑣的掛號排隊&#xff0c;只需通過小程序預約服務&#xff0c;便能夠享受到合適的醫療資源。這使得用戶的就醫過程變得簡單高效&#xff0c;并…

Redis使用規范及優化

緩存設計 緩存方案 普通緩存 查詢數據時&#xff0c;先查找緩存&#xff0c;如果有延長緩存時間并返回。如果沒有&#xff0c;再去查找數據庫&#xff0c;將查詢的數據再寫到緩存&#xff0c;同時設置過期時間。如果是靜態熱點數據&#xff0c;可以不設置緩存失效時間。 冷…

IntelliJ最佳插件

基于 IntelliJ 平臺的 JetBrains IDE 可能是當今最常見的 IDE 之一。它們的受歡迎程度在 JVM 語言社區中尤其明顯&#xff0c;IntelliJ IDEA 仍然是大多數開發人員的首選 IDE。所有這一切都是在一些新競爭對手的出現和老競爭對手克服以前的缺點并重新加入競爭者的情況下實現的。…

【EI/SCOPUS檢索】第三屆計算機視覺、應用與算法國際學術會議(CVAA 2023)

第三屆計算機視覺、應用與算法國際學術會議&#xff08;CVAA 2023) The 3rd International Conference on Computer Vision, Application and Algorithm 2023年第三屆計算機視覺、應用與算法國際學術會議&#xff08;CVAA 2023&#xff09;主要圍繞計算機視覺、計算機應用、計…

PPT顏色又丑又亂怎么辦?

一、設計一套PPT時&#xff0c;可以從這5個方面進行設計 二、PPT顏色 &#xff08;一&#xff09;、PPT常用顏色分類 一個ppt需要主色、輔助色、字體色、背景色即可。 &#xff08;二&#xff09;、搭建PPT色彩系統 設計ppt時&#xff0c;根據如下幾個步驟&#xff0c;依次選…

Arduino驅動紅外二氧化碳傳感器(氣體傳感器篇)

目錄 1、傳感器特性 2、驅動程序 紅外激光傳感器是將成熟的紅外吸收氣體檢測技術與精密光路設計、精良電路設計緊密結合而制作出的高性能傳感器,具有高靈敏度、高分辨率、低功耗,響應快、抗水汽干擾、不中毒、穩定性高、使用壽命長等特點。本篇博文使用Arduino驅動紅外二氧…

Android學習之路(2) 設置視圖

一、設置視圖寬高 ? 在Android開發中&#xff0c;可以使用LayoutParams類來設置視圖&#xff08;View&#xff09;的寬度和高度。LayoutParams是一個用于布局的參數類&#xff0c;用于指定視圖在父容器中的位置和大小。 ? 下面是設置視圖寬度和高度的示例代碼&#xff1a; …

Win10基于 Anaconda 配置 Deeplabcut 環境

最近需要做動物行為學分析的相關研究&#xff0c;同時由于合作者只有 Windows 系統&#xff0c;于是只好在 Windows 中配置環境。說實話還真的是挺折磨的。。。 一、下載 Anaconda 可以通過清華源下載 Anaconda&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/anaconda/ar…

算法leetcode|70. 爬樓梯(rust重拳出擊)

文章目錄 70. 爬樓梯&#xff1a;樣例 1&#xff1a;樣例 2&#xff1a;提示&#xff1a; 分析&#xff1a;題解&#xff1a;rust&#xff1a;go&#xff1a;c&#xff1a;python&#xff1a;java&#xff1a; 70. 爬樓梯&#xff1a; 假設你正在爬樓梯。需要 n 階你才能到達樓…

奧威BI數據可視化工具:報表就是平臺,隨時自助分析

別的數據可視化工具&#xff0c;報表就只是報表&#xff0c;而奧威BI數據可視化工具&#xff0c;一張報表就約等于一個平臺&#xff0c;可隨時展開多維動態自助分析&#xff0c;按需分析&#xff0c;立得數據信息。 奧威BI是一款多維立體分析數據的數據可視化工具。它可以幫助…

電腦xinput1_3.dll丟失的解決方法?哪個解決方法更簡單

最近在打開軟件或者游戲的時候&#xff0c;電腦提示xinput1_3.dll文件丟失的錯誤。這個問題導致我無法運行某些游戲和應用程序。通過一番嘗試和研究&#xff0c;我找到了一些修復xinput1_3.dll文件丟失的方法&#xff0c;并在此分享給大家。 首先&#xff0c;我了解到xinput1_3…

如何使用PHP編寫爬蟲程序

在互聯網時代&#xff0c;信息就像一條無休無止的河流&#xff0c;源源不斷地涌出來。有時候我們需要從Web上抓取一些數據&#xff0c;以便分析或者做其他用途。這時候&#xff0c;爬蟲程序就顯得尤為重要。爬蟲程序&#xff0c;顧名思義&#xff0c;就是用來自動化地獲取Web頁…

NSI45030AT1G LED驅動器方案為汽車外部及內部照明恒流穩流器(CCR)方案

關于線性恒流調節器&#xff08;CCR&#xff09;&#xff1a;是一種用于控制電流的穩定輸出。它通常由一個功率晶體管和一個參考電流源組成。CCR的工作原理是通過不斷調節功率晶體管的導通時間來維持輸出電流的恒定。當輸出電流超過設定值時&#xff0c;CCR會減少功率晶體管的導…