一文詳解Vue中渲染器的簡(jiǎn)單實(shí)現(xiàn)
一、渲染器
渲染器用于完成渲染操作,比如在瀏覽器平臺(tái)上渲染器可以將虛擬DOM轉(zhuǎn)換為真實(shí)DOM。
二、一個(gè)簡(jiǎn)單例子理解Vue中渲染器的工作過(guò)程
const { effect, ref } = VueReactivity function renderer(domString, container) { container.innerHTML = domString } const count = ref(1) effect(() => { renderer(`<h1>${count.value}</h1>`, document.getElementById('app')) }) count.value++
我們通過(guò)@vue/reactivity
引用vue的響應(yīng)式API,使用其中的effect
(副作用函數(shù))和ref
(生成響應(yīng)式數(shù)據(jù))使得count
和renderer
建立聯(lián)系,這樣當(dāng) count
變化時(shí),會(huì)調(diào)用renderer
處理渲染,將h1
標(biāo)簽掛載到id為app
的元素上。這就是一個(gè)簡(jiǎn)易的渲染器實(shí)現(xiàn)。
三、渲染器涉及的幾種操作: 掛載、更新、卸載
假設(shè)我們已經(jīng)有了一個(gè)渲染器renderer
:
const renderer = createRenderer()
有一個(gè)用于描述元素節(jié)點(diǎn)的數(shù)據(jù)vnode
:類似下面的結(jié)構(gòu)
// type為類型,children為子節(jié)點(diǎn) const vnode = { type: 'h1', children: 'hello' }
可以看到這是一個(gè)內(nèi)部有hello文本的<h1>
節(jié)點(diǎn)
那么在渲染器實(shí)際工作過(guò)程中會(huì)有如下的幾種情況:
renderer.render(vnode, document.querySelector('#app'))
此時(shí)涉及到的操作是掛載,只需要將vnode變?yōu)镈OM元素放在app元素內(nèi)部即可
renderer.render(newVNode, document.querySelector('#app'))
由于已經(jīng)有舊的節(jié)點(diǎn)存在,所以不能簡(jiǎn)單的直接掛載,需要對(duì)新舊節(jié)點(diǎn)進(jìn)行對(duì)比,找到需要更新的部分再改變,此時(shí)的操作就是更新,為了處理更新,渲染器中需要有對(duì)應(yīng)的邏輯。
renderer.render(null, document.querySelector('#app'))
新的節(jié)點(diǎn)為null則意味這渲染器需要清空app
元素內(nèi)的內(nèi)容,此時(shí)涉及的操作是卸載
根據(jù)對(duì)以上三種情況的處理,可以將渲染器寫為:
function createRenderer() { function patch(n1, n2, container) { } function render(vnode, container) { if (vnode) { // 新 vnode 存在,將其與舊 vnode 一起傳遞給 patch 函數(shù)進(jìn)行打補(bǔ)丁 patch(container._vnode, vnode, container) } else { if (container._vnode) { // 舊 vnode 存在,且新 vnode 不存在,說(shuō)明是卸載(unmount)操作 // 只需要將 container 內(nèi)的 DOM 清空即可 container.innerHTML = '' } } // 把 vnode 存儲(chǔ)到 container._vnode 下,即后續(xù)渲染中的舊 vnode container._vnode = vnode } return { render } }
使用createRenderer重復(fù)上面三次渲染,分析過(guò)程:
// 首次渲染 renderer.render(vnode, document.querySelector('#app')) // 第二次渲染 renderer.render(newVNode, document.querySelector('#app')) //第三次渲染 renderer.render(null, document.querySelector('#app'))
- 首次渲染:
vnode
被渲染,并存在container._vnode
中; - 第二次渲染: 新舊
vnode
均存在,需要在patch
函數(shù)中進(jìn)行更新; - 第三次渲染: 新的
vnode
為null
判斷舊節(jié)點(diǎn)container._vnode
是否存在,若舊的vnode
存在,則處理為卸載;
四、如何實(shí)現(xiàn)一個(gè)與平臺(tái)無(wú)關(guān)的渲染器:
一個(gè)通用的渲染器應(yīng)該是是與平臺(tái)無(wú)關(guān)的,即渲染器的渲染功能不能只在某一個(gè)平臺(tái)中有效。 為了實(shí)現(xiàn)這個(gè)目標(biāo),我們首先來(lái)實(shí)現(xiàn)一個(gè)基于瀏覽器平臺(tái)的渲染器,觀察在哪些步驟中使用到了和瀏覽器相關(guān)的API,之后可以考慮將這些API抽離,作為配置項(xiàng),這樣就可以實(shí)現(xiàn)一個(gè)通用的渲染器。
1.實(shí)現(xiàn)一個(gè)依賴瀏覽器API的渲染器
以之前的的vnode
為例:
const vnode = { type: 'h1', children: 'hello' }
type
為標(biāo)簽類型、children
為子元素。
在以上實(shí)現(xiàn)的基礎(chǔ)上我們首先完善patch
函數(shù),用于處理節(jié)點(diǎn)的掛載和更新情況
patch(container._vnode, vnode, container)
通過(guò)之前處理的代碼,可以看到,patch
函數(shù)會(huì)接受三個(gè)參數(shù),分別是 舊的節(jié)點(diǎn)、新的節(jié)點(diǎn)、以及容器, 我們?cè)谄渲袑?duì)舊節(jié)點(diǎn)參數(shù)進(jìn)行判斷,從而處理不同的操作:目前只分析掛載的情況:
function patch(n1, n2, container) { if (!n1) { mountElement(n2, container) } else { // } }
如上代碼所示: 當(dāng)n1
即舊節(jié)點(diǎn)不存在時(shí)證明是掛載操作,則直接調(diào)用mountElement
對(duì)新的節(jié)點(diǎn)進(jìn)行掛載處理
function mountElement(vnode, container) { const el = createElement(vnode.type) if (typeof vnode.children === 'string') { el.textContent = vnode.children } container.appendChild(el) }
在mountElement
中我們對(duì)掛載的邏輯進(jìn)行了簡(jiǎn)單的處理:
1. 使用createElement
根據(jù)vnode中的type
的值去創(chuàng)建對(duì)應(yīng)的DOM類型: 如vnode.type='h1'
則el為<h1></h1>
2. 對(duì)vnode.children進(jìn)行判斷: 如果vnode.children
為字符串類型則證明子節(jié)點(diǎn)是一個(gè)文本節(jié)點(diǎn),直接使用元素的textContent
屬性設(shè)置元素。
3. 調(diào)用appendChild將處理好的元素添加到目標(biāo)容器中。由此掛載完成。
2.實(shí)現(xiàn)一個(gè)通用的渲染器
以上實(shí)現(xiàn)的渲染器對(duì)于瀏覽器的API是有依賴的,其中的appendChild
、createElement
、textContent
都是需要瀏覽器環(huán)境才能執(zhí)行的API,如果要讓渲染器能夠通用,就需要去除對(duì)這些API的依賴。
解決辦法:將這些API作為配置項(xiàng)傳入渲染器創(chuàng)建函數(shù),這樣我們可以自己定義createRenderer
使用哪些API去執(zhí)行渲染操作,從而使得渲染器的工作不再依賴于某一平臺(tái)。
const renderer2 = createRenderer({ //創(chuàng)建元素 createElement(tag) { return { tag } }, //設(shè)置元素文本內(nèi)容 setElementText(el, text) { console.log(`設(shè)置 ${JSON.stringify(el)} 的文本內(nèi)容:${text}`) el.text = text }, //將元素插入目標(biāo)節(jié)點(diǎn) insert(el, parent, anchor = null) { parent.children = el } })
我們將創(chuàng)建元素的邏輯和API放入createElement
中,將設(shè)置文本內(nèi)容的API放入setElementText
中, 將元素掛載到容器的API過(guò)程放入insert
中。 再使用傳入的配置去調(diào)用mountElement
function mountElement(vnode, container) { const el = createElement(vnode.type) if (typeof vnode.children === 'string') { setElementText(el, vnode.children) } insert(el, container) }
這樣我們可以通過(guò)傳入的createRenderer
函數(shù)的options去
配置,不同平臺(tái)中使用什么樣的方法去執(zhí)行元素的創(chuàng)建、元素內(nèi)容的處理以及元素掛載等操作。
由此整個(gè)渲染函數(shù)如下:
function createRenderer(options) { const { createElement, insert, setElementText } = options function mountElement(vnode, container) { const el = createElement(vnode.type) if (typeof vnode.children === 'string') { setElementText(el, vnode.children) } insert(el, container) } function patch(n1, n2, container) { if (!n1) { mountElement(n2, container) } else { // } } function render(vnode, container) { if (vnode) { // 新 vnode 存在,將其與舊 vnode 一起傳遞給 patch 函數(shù)進(jìn)行打補(bǔ)丁 patch(container._vnode, vnode, container) } else { if (container._vnode) { // 舊 vnode 存在,且新 vnode 不存在,說(shuō)明是卸載(unmount)操作 // 只需要將 container 內(nèi)的 DOM 清空即可 container.innerHTML = '' } } // 把 vnode 存儲(chǔ)到 container._vnode 下,即后續(xù)渲染中的舊 vnode container._vnode = vnode } return { render } }
由此一個(gè)簡(jiǎn)單的渲染器已經(jīng)實(shí)現(xiàn),但在實(shí)際情況下元素的掛載和更新還有更多的細(xì)節(jié)現(xiàn)需要處理,如元素上的屬性、class、事件如何被正確的掛載,如何提升渲染器更新的效率(diff算法)等,這些將在后續(xù)文章中進(jìn)行討論。
以上就是一文詳解Vue中渲染器的簡(jiǎn)單實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于Vue實(shí)現(xiàn)渲染器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue中使用axios post上傳頭像/圖片并實(shí)時(shí)顯示到頁(yè)面的方法
今天小編就為大家分享一篇vue中使用axios post上傳頭像/圖片并實(shí)時(shí)顯示到頁(yè)面的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09vue框架和react框架的區(qū)別以及各自的應(yīng)用場(chǎng)景使用
這篇文章主要介紹了vue框架和react框架的區(qū)別以及各自的應(yīng)用場(chǎng)景使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10vue2實(shí)現(xiàn)搜索結(jié)果中的搜索關(guān)鍵字高亮的代碼
這篇文章主要介紹了vue2實(shí)現(xiàn)搜索結(jié)果中的搜索關(guān)鍵字高亮的代碼,需要的朋友可以參考下2018-08-08教你使用vue-autofit 一行代碼搞定自適應(yīng)可視化大屏
這篇文章主要為大家介紹了使用vue-autofit 一行代碼搞定自適應(yīng)可視化大屏教程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05element-ui使用導(dǎo)航欄跳轉(zhuǎn)路由的用法詳解
今天小編就為大家分享一篇element-ui使用導(dǎo)航欄跳轉(zhuǎn)路由的用法詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08vue實(shí)現(xiàn)標(biāo)簽頁(yè)切換/制作tab組件詳細(xì)教程
在項(xiàng)目開發(fā)中需要使用vue實(shí)現(xiàn)tab頁(yè)簽切換功能,所以這里總結(jié)下,這篇文章主要給大家介紹了關(guān)于vue實(shí)現(xiàn)標(biāo)簽頁(yè)切換/制作tab組件的相關(guān)資料,需要的朋友可以參考下2023-11-11