Vue createRenderer 自定義渲染器從入門(mén)到實(shí)戰(zhàn)
?? Vue 3它不僅能高效渲染瀏覽器 DOM,還能實(shí)現(xiàn)小程序、Native 等多端運(yùn)行。而支撐這一切的核心,就是 createRenderer 函數(shù)。它允許我們自定義渲染邏輯,擺脫 Vue 內(nèi)置 DOM 渲染的限制,打造適配任意平臺(tái)的渲染器
一、自定義 DOM 渲染器
示例重點(diǎn)實(shí)現(xiàn)支持事件綁定的 patchProp 方法,還會(huì)加入虛擬節(jié)點(diǎn)更新案例,直觀看到渲染器的更新流程。
完整可運(yùn)行代碼
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>Vue 自定義渲染器入門(mén)示例</title>
<!-- 引入 Vue 3 完整版,方便瀏覽器直接運(yùn)行 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
<!-- 渲染掛載容器 -->
<div id="app"></div>
<script>
// 從 Vue 中解構(gòu)出 createRenderer 和 h 函數(shù)
const { createRenderer, h } = Vue;
// 1. 創(chuàng)建自定義渲染器:傳入平臺(tái)渲染配置對(duì)象
const renderer = createRenderer({
// 創(chuàng)建元素節(jié)點(diǎn):根據(jù)標(biāo)簽名創(chuàng)建 DOM 元素
createElement(tag) {
console.log(`[渲染步驟] 創(chuàng)建元素節(jié)點(diǎn):<${tag}>`);
return document.createElement(tag);
},
// 更新元素屬性:核心改造!支持普通屬性 + 事件綁定(onXXX 格式)
patchProp(el, key, prevValue, nextValue) {
// 判斷是否是事件屬性(以 on 開(kāi)頭,且第二個(gè)字母大寫(xiě),如 onClick、onInput)
const isEvent = key.startsWith('on') && /^on[A-Z]/.test(key);
if (isEvent) {
// 提取事件名(去掉 on 前綴,轉(zhuǎn)為小寫(xiě),如 onClick -> click)
const eventName = key.slice(2).toLowerCase();
// 移除舊的事件監(jiān)聽(tīng)(如果有舊值)
if (prevValue) {
el.removeEventListener(eventName, prevValue);
}
// 綁定新的事件監(jiān)聽(tīng)(如果有新值)
if (nextValue) {
el.addEventListener(eventName, nextValue);
console.log(`[渲染步驟] 綁定事件:${eventName},回調(diào)函數(shù)已掛載`);
}
} else {
// 普通屬性:直接用 setAttribute 處理
if (nextValue === undefined || nextValue === null) {
el.removeAttribute(key);
console.log(`[渲染步驟] 移除普通屬性:${key}`);
} else {
el.setAttribute(key, nextValue);
console.log(`[渲染步驟] 更新普通屬性:${key} = ${nextValue}`);
}
}
},
// 插入元素:將子元素插入到父元素的指定位置
insert(el, parent, anchor) {
console.log(`[渲染步驟] 插入元素:將 <${el.tagName.toLowerCase()}> 插入到 <${parent.tagName.toLowerCase()}>`);
parent.insertBefore(el, anchor || null);
},
// 移除元素:從父節(jié)點(diǎn)中移除當(dāng)前元素
remove(el) {
console.log(`[渲染步驟] 移除元素:<${el.tagName.toLowerCase()}>`);
el.parentNode.removeChild(el);
},
// 創(chuàng)建文本節(jié)點(diǎn):創(chuàng)建 DOM 文本節(jié)點(diǎn)
createText(text) {
console.log(`[渲染步驟] 創(chuàng)建文本節(jié)點(diǎn):${text}`);
return document.createTextNode(text);
},
// 更新文本節(jié)點(diǎn):修改文本節(jié)點(diǎn)的內(nèi)容
setText(node, text) {
console.log(`[渲染步驟] 更新文本節(jié)點(diǎn):${node.nodeValue} → ${text}`);
node.nodeValue = text;
}
});
// 2. 獲取掛載容器
const app = document.getElementById('app');
// 3. 初始虛擬節(jié)點(diǎn)(無(wú)事件)
const vnode1 = h('div', { title: '初始節(jié)點(diǎn)' }, 'Hello initial vnode');
// 4. 1秒后更新的虛擬節(jié)點(diǎn)(帶 onClick 事件)
const vnode2 = h(
'div',
{
onClick() {
console.log('更新了!點(diǎn)擊事件觸發(fā)成功~');
},
title: '更新后節(jié)點(diǎn)(帶點(diǎn)擊事件)' // 同時(shí)更新普通屬性
},
'hello world'
);
// 5. 先渲染初始虛擬節(jié)點(diǎn)
renderer.render(vnode1, app);
// 6. 1秒后更新虛擬節(jié)點(diǎn),觸發(fā) patchProp 處理事件和屬性更新
setTimeout(() => {
console.log('==== 開(kāi)始更新虛擬節(jié)點(diǎn) ====');
renderer.render(vnode2, app);
}, 1000);
</script>
</body>
</html>
運(yùn)行效果
- 打開(kāi)瀏覽器運(yùn)行該 HTML 文件,頁(yè)面先顯示
Hello initial vnode,鼠標(biāo)懸浮彈出「初始節(jié)點(diǎn)」提示; - 1秒后,文本自動(dòng)更新為
hello world,懸浮提示變?yōu)椤父潞蠊?jié)點(diǎn)(帶點(diǎn)擊事件)」; - 點(diǎn)擊文本所在的
div,控制臺(tái)打印更新了!點(diǎn)擊事件觸發(fā)成功~; - 全程控制臺(tái)會(huì)清晰打印渲染、更新、事件綁定的日志,直觀看到自定義渲染器的完整執(zhí)行流程。
二、核心拆解:這段代碼到底在做什么?
我們逐部分拆解代碼,理解 createRenderer 的核心組成和工作邏輯,重點(diǎn)解析新增的虛擬節(jié)點(diǎn)更新案例。
1. 核心引入:createRenderer和h函數(shù)
const { createRenderer, h } = Vue;
這兩個(gè)函數(shù)是實(shí)現(xiàn)自定義渲染的關(guān)鍵,各自承擔(dān)核心職責(zé):
createRenderer:Vue 3 提供的渲染器工廠函數(shù),接收一套「平臺(tái)渲染接口」,返回一個(gè)具備完整渲染能力的自定義渲染器實(shí)例。這個(gè)實(shí)例擁有createApp和render方法,和 Vue 默認(rèn)的 DOM 渲染器功能一致,只是渲染邏輯由我們自定義。h函數(shù):全稱createVNode,核心作用是構(gòu)建虛擬 DOM 節(jié)點(diǎn)(VNode)。它接收標(biāo)簽名/組件、屬性對(duì)象、子節(jié)點(diǎn)/文本內(nèi)容,返回一個(gè)標(biāo)準(zhǔn)的 VNode 對(duì)象,作為渲染器的輸入數(shù)據(jù)。
2. 核心步驟:創(chuàng)建自定義渲染器(createRenderer)
const renderer = createRenderer({ /* 渲染配置對(duì)象 */ });
createRenderer 接收一個(gè)配置對(duì)象作為唯一參數(shù),這個(gè)對(duì)象必須實(shí)現(xiàn) 6 個(gè)核心方法,它們是渲染器與「目標(biāo)平臺(tái)」的交互橋梁,負(fù)責(zé)將 VNode 轉(zhuǎn)換為目標(biāo)平臺(tái)的真實(shí)節(jié)點(diǎn)(這里是瀏覽器 DOM)。
6 個(gè)核心渲染方法詳解(DOM 平臺(tái))
| 方法名 | 核心作用 | 入?yún)⒄f(shuō)明 |
|---|---|---|
| createElement | 創(chuàng)建元素節(jié)點(diǎn) | tag:標(biāo)簽名(如 'div'、'p'),返回創(chuàng)建好的 DOM 元素 |
| patchProp | 更新元素屬性 | el:真實(shí) DOM 元素、key:屬性名、prevValue:舊屬性值、nextValue:新屬性值 |
| insert | 插入元素 | el:要插入的 DOM 元素、parent:父 DOM 元素、anchor:插入?yún)⒖脊?jié)點(diǎn)(null 則插入末尾) |
| remove | 移除元素 | el:要移除的 DOM 元素 |
| createText | 創(chuàng)建文本節(jié)點(diǎn) | text:文本內(nèi)容,返回創(chuàng)建好的 DOM 文本節(jié)點(diǎn) |
| setText | 更新文本節(jié)點(diǎn) | node:真實(shí) DOM 文本節(jié)點(diǎn)、text:新的文本內(nèi)容 |
關(guān)鍵亮點(diǎn):patchProp支持事件綁定
本次改造的核心是 patchProp 方法,它不僅能處理 title 這類普通屬性,還能識(shí)別 onClick 這類事件屬性,實(shí)現(xiàn) DOM 事件的綁定與移除:
- 先判斷屬性是否為
onXXX格式的事件; - 提取原生事件名(
onClick→click); - 遵循「先清后綁」原則,避免重復(fù)綁定導(dǎo)致多次觸發(fā)。
3. 新增亮點(diǎn):虛擬節(jié)點(diǎn)更新案例(核心解析)
自定義渲染器如何處理 VNode 更新,這也是 Vue 響應(yīng)式更新的底層縮影:
// 2. 獲取掛載容器
const app = document.getElementById('app');
// 3. 初始虛擬節(jié)點(diǎn)(無(wú)事件)
const vnode1 = h('div', { title: '初始節(jié)點(diǎn)' }, 'Hello initial vnode');
// 4. 1秒后更新的虛擬節(jié)點(diǎn)(帶 onClick 事件)
const vnode2 = h(
'div',
{
onClick() {
console.log('更新了!點(diǎn)擊事件觸發(fā)成功~');
},
title: '更新后節(jié)點(diǎn)(帶點(diǎn)擊事件)' // 同時(shí)更新普通屬性
},
'hello world'
);
// 5. 先渲染初始虛擬節(jié)點(diǎn)
renderer.render(vnode1, app);
// 6. 1秒后更新虛擬節(jié)點(diǎn),觸發(fā) patchProp 處理事件和屬性更新
setTimeout(() => {
console.log('==== 開(kāi)始更新虛擬節(jié)點(diǎn) ====');
renderer.render(vnode2, app);
}, 1000);
這段代碼的核心邏輯:
- 初始渲染:調(diào)用
renderer.render(vnode1, app),渲染器將vnode1轉(zhuǎn)換為真實(shí) DOM,插入到掛載容器中,完成首次渲染; - 延遲更新:1 秒后調(diào)用
renderer.render(vnode2, app),渲染器會(huì)自動(dòng)對(duì)比vnode1和vnode2的差異(屬性、文本內(nèi)容); - 差異更新:
- 對(duì)于
title屬性:觸發(fā)patchProp方法,將舊值「初始節(jié)點(diǎn)」更新為新值「更新后節(jié)點(diǎn)(帶點(diǎn)擊事件)」; - 對(duì)于
onClick事件:觸發(fā)patchProp方法,綁定新的點(diǎn)擊事件回調(diào); - 對(duì)于文本內(nèi)容:觸發(fā)
setText方法,將「Hello initial vnode」更新為「hello world」;
- 對(duì)于
- 無(wú)全量重建:整個(gè)更新過(guò)程沒(méi)有刪除舊 DOM 再創(chuàng)建新 DOM,而是只更新有差異的部分,這也是 Vue 渲染高效的核心原因。
4. 掛載應(yīng)用的兩種方式
案例使用 renderer.render(vnode, container) 直接渲染 VNode,除此之外,也可以通過(guò) renderer.createApp(component).mount(container) 掛載組件,兩種方式均有效:
- 直接渲染 VNode:更靈活,適合手動(dòng)控制渲染流程(如本次的延遲更新案例);
- 通過(guò)
createApp掛載:更貼近日常 Vue 開(kāi)發(fā),適合組件化開(kāi)發(fā)場(chǎng)景。
三、深入理解:自定義渲染器的工作流程
整個(gè)渲染與更新過(guò)程可以總結(jié)為 4 個(gè)核心步驟,形成一個(gè)完整的閉環(huán):
- 生成 VNode:通過(guò)
h函數(shù)創(chuàng)建標(biāo)準(zhǔn) VNode,提供渲染的數(shù)據(jù)源; - 首次渲染:渲染器調(diào)用 6 個(gè)核心方法,將 VNode 轉(zhuǎn)換為真實(shí)節(jié)點(diǎn),插入到掛載容器中;
- VNode 對(duì)比:更新時(shí),渲染器對(duì)比新舊 VNode,找出屬性、文本等差異;
- 差異更新:針對(duì)差異部分,調(diào)用對(duì)應(yīng)的
patchProp、setText等方法,更新真實(shí)節(jié)點(diǎn),無(wú)需全量重建。
到此這篇關(guān)于Vue createRenderer 自定義渲染器從入門(mén)到實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)Vue createRenderer 自定義渲染器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue3動(dòng)態(tài)組件component不生效問(wèn)題解決方法
動(dòng)態(tài)組件component是Vue中非常實(shí)用的一個(gè)功能,它可以根據(jù)條件動(dòng)態(tài)切換不同的組件,在Vue3中使用方法和Vue2基本一致,在vue3使用component動(dòng)態(tài)組件展示組件時(shí),組件就是不展示顯示空白,所以本文記錄了Vue3動(dòng)態(tài)組件component不生效問(wèn)題解決方法,需要的朋友可以參考下2024-08-08
使用Vue3和Vite實(shí)現(xiàn)對(duì)低版本瀏覽器的兼容
在使用Vite和Vue3構(gòu)建的JavaScript項(xiàng)目中,確保對(duì)低版本瀏覽器的兼容性是一個(gè)重要的考慮因素,以下是一些具體的解決方案和步驟,可以幫助你實(shí)現(xiàn)這一目標(biāo),需要的朋友可以參考下2024-11-11
elementui?el-upload一次請(qǐng)求上傳多個(gè)文件的實(shí)現(xiàn)
使用ElementUI?Upload的時(shí)候發(fā)現(xiàn)如果是默認(rèn)方案,上傳多張圖片并不是真正的一次上傳多張,本文就來(lái)介紹一下elementui?el-upload一次請(qǐng)求上傳多個(gè)文件的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2023-10-10
vue.js集成echarts時(shí)遇到的一些問(wèn)題總結(jié)
這篇文章主要給大家總結(jié)介紹了關(guān)于vue.js集成echarts遇到的一些問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04
vue中簡(jiǎn)單彈框dialog的實(shí)現(xiàn)方法
下面小編就為大家分享一篇vue中簡(jiǎn)單彈框dialog的實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-02-02
Element?Plus在el-form-item中設(shè)置justify-content無(wú)效的解決方案
這篇文章主要介紹了Element?Plus在el-form-item中設(shè)置justify-content無(wú)效的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-10-10

