組件化——組件的實現原理

渲染器主要負責將虛擬 DOM 渲染為真實 DOM,我們只需要使用虛擬 DOM 來描述最終呈現的內容即可。但當我們編寫比較復雜的頁面時,用來描述頁面結構的虛擬 DOM 的代碼量會變得越來越多,或者說頁面模板會變得越來越大。這時,我們就需要組件化的能力。有了組件,我們就可以將一個大的頁面拆分為多個部分,每一個部分都可以作為單獨的組件,這些組件共同組成完整的頁面。組件化的實現同樣需要渲染器的支持,從現在開始,我們將詳細討論 Vue.js 中的組件化。

1、渲染組件

從用戶的角度來看,一個有狀態組件就是一個選項對象,如下面的代碼所示:

01 // MyComponent 是一個組件,它的值是一個選項對象
02 const MyComponent = {
03   name: 'MyComponent',
04   data() {
05     return { foo: 1 }
06   }
07 }

但是,如果從渲染器的內部實現來看,一個組件則是一個特殊類型的虛擬 DOM 節點。例如,為了描述普通標簽,我們用虛擬節點的 vnode.type 屬性來存儲標簽名稱,如下面的代碼所示:

01 // 該 vnode 用來描述普通標簽
02 const vnode = {
03   type: 'div'
04   // ...
05 }

為了描述片段,我們讓虛擬節點的 vnode.type 屬性的值為Fragment,例如:

01 // 該 vnode 用來描述片段
02 const vnode = {
03   type: Fragment
04   // ...
05 }

為了描述文本,我們讓虛擬節點的 vnode.type 屬性的值為Text,例如:

01 // 該 vnode 用來描述文本節點
02 const vnode = {
03   type: Text
04   // ...
05 }

渲染器的 patch 函數證明了上述內容,如下是我們實現的 patch 函數的代碼:

01 function patch(n1, n2, container, anchor) {
02   if (n1 && n1.type !== n2.type) {
03     unmount(n1)
04     n1 = null
05   }
06
07   const { type } = n2
08
09   if (typeof type === 'string') {
10     // 作為普通元素處理
11   } else if (type === Text) {
12     // 作為文本節點處理
13   } else if (type === Fragment) {
14     // 作為片段處理
15   }
16 }

可以看到,渲染器會使用虛擬節點的 type 屬性來區分其類型。對于不同類型的節點,需要采用不同的處理方法來完成掛載和更新。

實際上,對于組件來說也是一樣的。為了使用虛擬節點來描述組件,我們可以用虛擬節點的 vnode.type 屬性來存儲組件的選項對象,例如:

01 // 該 vnode 用來描述組件,type 屬性存儲組件的選項對象
02 const vnode = {
03   type: MyComponent
04   // ...
05 }

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

01 function patch(n1, n2, container, anchor) {
02   if (n1 && n1.type !== n2.type) {
03     unmount(n1)
04     n1 = null
05   }
06
07   const { type } = n2
08
09   if (typeof type === 'string') {
10     // 作為普通元素處理
11   } else if (type === Text) {
12     // 作為文本節點處理
13   } else if (type === Fragment) {
14     // 作為片段處理
15   } else if (typeof type === 'object') {
16     // vnode.type 的值是選項對象,作為組件來處理
17     if (!n1) {
18       // 掛載組件
19       mountComponent(n2, container, anchor)
20     } else {
21       // 更新組件
22       patchComponent(n1, n2, anchor)
23     }
24   }
25 }

在上面這段代碼中,我們新增了一個 else if 分支,用來處理虛擬節點的 vnode.type 屬性值為對象的情況,即將該虛擬節點作為組件的描述來看待,并調用 mountComponent 和patchComponent 函數來完成組件的掛載和更新。

渲染器有能力處理組件后,下一步我們要做的是,設計組件在用戶層面的接口。這包括:用戶應該如何編寫組件?組件的選項對象必須包含哪些內容?以及組件擁有哪些能力?等等。實際上,組件本身是對頁面內容的封裝,它用來描述頁面內容的一部分。因此,一個組件必須包含一個渲染函數,即 render 函數,并且渲染函數的返回值應該是虛擬 DOM。換句話說,組件的渲染函數就是用來描述組件所渲染內容的接口,如下面的代碼所示:

01 const MyComponent = {
02   // 組件名稱,可選
03   name: 'MyComponent',
04   // 組件的渲染函數,其返回值必須為虛擬 DOM
05   render() {
06     // 返回虛擬 DOM
07     return {
08       type: 'div',
09       children: `我是文本內容`
10     }
11   }
12 }

這是一個最簡單的組件示例。有了基本的組件結構之后,渲染器就可以完成組件的渲染,如下面的代碼所示:

01 // 用來描述組件的 VNode 對象,type 屬性值為組件的選項對象
02 const CompVNode = {
03   type: MyComponent
04 }
05 // 調用渲染器來渲染組件
06 renderer.render(CompVNode, document.querySelector('#app'))

渲染器中真正完成組件渲染任務的是 mountComponent 函數,其具體實現如下所示:

01 function mountComponent(vnode, container, anchor) {
02   // 通過 vnode 獲取組件的選項對象,即 vnode.type
03   const componentOptions = vnode.type
04   // 獲取組件的渲染函數 render
05   const { render } = componentOptions
06   // 執行渲染函數,獲取組件要渲染的內容,即 render 函數返回的虛擬 DOM
07   const subTree = render()
08   // 最后調用 patch 函數來掛載組件所描述的內容,即 subTree
09   patch(null, subTree, container, anchor)
10 }

這樣,我們就實現了最基本的組件化方案。

2、組件狀態與自更新

在上一節中,我們完成了組件的初始渲染。接下來,我們嘗試為組件設計自身的狀態,如下面的代碼所示:

01 const MyComponent = {
02   name: 'MyComponent',
03   // 用 data 函數來定義組件自身的狀態
04   data() {
05     return {
06       foo: 'hello world'
07     }
08   },
09   render() {
10     return {
11       type: 'div',
12       children: `foo 的值是: ${this.foo}` // 在渲染函數內使用組件狀態
13     }
14   }
15 }

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

下面的代碼實現了組件自身狀態的初始化:

01 function mountComponent(vnode, container, anchor) {
02   const componentOptions = vnode.type
03   const { render, data } = componentOptions
04
05   // 調用 data 函數得到原始數據,并調用 reactive 函數將其包裝為響應式數據
06   const state = reactive(data())
07   // 調用 render 函數時,將其 this 設置為 state,
08   // 從而 render 函數內部可以通過 this 訪問組件自身狀態數據
09   const subTree = render.call(state, state)
10   patch(null, subTree, container, anchor)
11 }

如上面的代碼所示,實現組件自身狀態的初始化需要兩個步驟:

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

經過上述兩步工作后,我們就實現了對組件自身狀態的支持,以及在渲染函數內訪問組件自身狀態的能力。

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

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

相關文章

iperf3 網絡測試

iperf3 測試網絡的上下行帶寬 下載地址 https://iperf.fr/iperf-download.php 開啟服務器 開啟客戶端 常用命令 -c 代表客戶端-s 代表服務端-u 代表 udp-r 代表數據方向是否反向 https://baijiahao.baidu.com/s?id1731514357681464971&wfrspider&forpc

C++學習 --queue

目錄 1, 什么是queue 2, 創建queue 2-1, 標準數據類型 2-2, 自定義數據類型 2-3, 其他創建方式 3, 操作stack 3-1, 賦值 3-2, 插入元素(push) 3-3, 查詢元素 3…

Python簡直是萬能的,這5大主要用途你一定要知道!

從2015開始國內就開始慢慢接觸Python了,從16年開始Python就已經在國內的熱度更高了,目前也可以算的上"全民Python"了。 眾所周知小學生的教材里面已經有Python了,國家二級計算機證也需要學習Python了! 因為Python簡單…

編程語言發展史:布爾代數和機器語言

布爾代數是一種數學理論,用于描述和分析邏輯和布爾值的關系。它是由英國數學家George Boole在19世紀中期發明的,被認為是現代計算機科學的基礎之一。布爾代數的發明使得邏輯運算可以被表示為代數運算,從而為計算機科學的發展奠定了基礎。 在…

PTA 7-4 數列求和-加強版

7-4 數列求和-加強版 分數 20 全屏瀏覽題目 作者 DS課程組 單位 浙江大學 給定某數字A(1≤A≤9)以及非負整數N(0≤N≤100000),求數列之和SAAAAAA?AA?A(N個A)。例如A1, N3時,S1…

Unity、UE和Godot的優劣對比

先占位。。。。。。 首先說Unity和UE這兩家公司,是行業的兩座燈塔,對整個游戲引擎的這個行業的發展具有這種指導性的這種作作用。這兩個引擎我從2016年開始就一直在用,結合一下業內的共識,一般來說認為呢,Unity更擅長移…

2023全球邊緣計算大會深圳站-核心PPT資料下載

一、峰會簡介 邊緣計算,是指在靠近物或數據源頭的一側,采用網絡、計算、存儲、應用核心能力為一體的開放平臺,就近提供最近端服務。其應用程序在邊緣側發起,產生更快的網絡服務響應,滿足行業在實時業務、應用智能、安…

LeetCode算法題解(動態規劃,背包問題)|LeetCode416. 分割等和子集

LeetCode416. 分割等和子集 題目鏈接:416. 分割等和子集 題目描述: 給你一個 只包含正整數 的 非空 數組 nums 。請你判斷是否可以將這個數組分割成兩個子集,使得兩個子集的元素和相等。 示例 1: 輸入:nums [1,5,…

Linux中的進程程序替換

Linux中的進程程序替換 1. 替換原理2. 替換函數3. 函數解釋4. 命名理解程序替換的意義 1. 替換原理 替換原理 用fork創建子進程后執行的是和父進程相同的程序(但有可能執行不同的代碼分支),子進程往往要調用一種exec函數以執行另一個程序。當進程調用一種exec函數時,該進程的…

[Docker]九.Docker compose講解

docker-compose 是 docker 官方的一個開源項目,可以實現對 docker 容器集群的快速編排, docker-compose 通過一個 配置文件 來管理多個 Docker 容器,在配置文件中,所有的容器通過 services 來定義,然后使用 docker-compose腳本 來 啟動&am…

Nuxt.js Next.js Nest.js

Nuxt.js和Next.js都是服務端渲染框架(SSR),屬于前端框架,Nest.js則是node框架,屬于后端框架。 其中Nuxt.js是vue的ssr框架,Next.js是react的ssr框架。 都是比vue和react更上層的前端框架。 文章目錄 1.SSR2.Nuxt2.1 Nuxt的下載2.2 Nuxt的集成2.3 Nuxt…

HuggingFace-利用BERT預訓練模型實現中文情感分類(下游任務)

準備數據集 使用編碼工具 首先需要加載編碼工具,編碼工具可以將抽象的文字轉成數字,便于神經網絡后續的處理,其代碼如下: # 定義數據集 from transformers import BertTokenizer, BertModel, AdamW # 加載tokenizer token Ber…

cobol基本動詞

cobol基本動詞 基本動詞用于過程部中的數據處理。每個語句總是以cobol動詞開頭。 input(輸入)/output(輸出) 輸入輸出動詞用于從用戶獲取數據。并顯示cobol程序的輸出。 accept 用于從操作系統或者用戶獲取數據,例如日…

langchain 部署組件-LangServe

原文:🦜?🏓 LangServe | 🦜?🔗 Langchain LangServe 🚩 We will be releasing a hosted version of LangServe for one-click deployments of LangChain applications. Sign up here to get on the wa…

OpenLayers入門,OpenLayers6的WebGLPointsLayer圖層樣式和運算符詳解,四種symbolType類型案例

專欄目錄: OpenLayers入門教程匯總目錄 前言 本章講解使用OpenLayers6的WebGL圖層顯示大量點情況下,列舉出所有WebGLPointsLayer圖層所支持的所有樣式運算符大全。 補充說明 本篇主要介紹OpenLayers6.x版本的webgl圖層,OpenLayers7.x和OpenLayers8.x主要更新內容就是webgl…

GB28181學習(十七)——基于jrtplib實現tcp被動和主動發流

前言 GB/T28181-2022實時流的傳輸方式介紹:https://blog.csdn.net/www_dong/article/details/134255185 基于jrtplib實現tcp被動和主動收流介紹:https://blog.csdn.net/www_dong/article/details/134451387 本文主要介紹下級平臺或設備發流功能&#…

生活如果真能像隊列一樣的話

生活如果真能像隊列一樣,那該多好啊。 —————————————————————————————————————————— 背包,隊列 可以先看他們的API:都含有一個無參構造函數,添加單個元素的方法,測試集合…

php項目從寶塔面板切換轉到phpstudy小皮面板

寶塔面板轉phpstudy面板 版本 寶塔面板8.0.1 phpstudy面板8.1.1.3 步驟 1、寶塔面板,找到項目文件夾,打包、下載到本地、解壓 2、本地windows系統安裝phpstudy面板,選擇盡可能一樣的配置 比如寶塔php7.4.33,可能phpstudy面板只有php7.4.3,也行 大環境一定要一致,比如…

力扣算法練習BM46—最小的K個數

題目 給定一個長度為 n 的可能有重復值的數組,找出其中不去重的最小的 k 個數。例如數組元素是4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4(任意順序皆可)。 數據范圍:0≤k,n≤10000,數組中每個數的大小0≤val≤1000 要…

linux signal 機制

ref: Linux操作系統學習筆記(十六)進程間通信之信號 | Ty-Chens Home https://www.cnblogs.com/renxinyuan/p/3867593.html 當執行kill -9 PID時系統發生了什么 -