一、為什么需要它們?從模板到虛擬DOM的進(jìn)化史
1.1 模板的局限(Vue2時(shí)代)
// 傳統(tǒng)模板編譯流程
<template>
<div>{{ message }}</div>
</template>
↓ 編譯 ↓
function render() {
return _c('div', [_v(_s(message))])
}
痛點(diǎn):
- 動(dòng)態(tài)組件需要
<component :is>
特殊語(yǔ)法 - 無(wú)法享受JavaScript完整的編程能力
- 復(fù)雜邏輯需要強(qiáng)制拆分到模板和script兩個(gè)區(qū)域
1.2 虛擬DOM的革命性
interface VNode {
type: string | Component // 元素類型
props: Record<string, any> // 屬性
children: VNode[] | string // 子節(jié)點(diǎn)
key?: string | number // 優(yōu)化標(biāo)識(shí)
// ...其他內(nèi)部屬性
}
設(shè)計(jì)哲學(xué):
- JavaScript對(duì)象描述DOM結(jié)構(gòu)(輕量級(jí)"藍(lán)圖")
- 實(shí)現(xiàn)跨平臺(tái)渲染能力(Web/小程序/Canvas)
二、h函數(shù)與createVNode的關(guān)系解密
2.1 官方定義對(duì)比
// 源碼中的真實(shí)定義(vue-next/packages/runtime-core/src/vnode.ts)
export const createVNode = (
__DEV__ ? createVNodeWithArgsTransform : _createVNode
) as typeof _createVNode
// h函數(shù)是createVNode的類型重載版本
export function h(type: any, props?: any, children?: any): VNode {
// ...處理不同參數(shù)情況
return createVNode(type, props, children)
}
三、核心用法詳解(帶場(chǎng)景化示例)
3.1 基礎(chǔ)元素創(chuàng)建
// 創(chuàng)建帶事件監(jiān)聽(tīng)的按鈕
const button = h(
'button', // 元素類型
{
class: 'btn-primary', // class綁定
onClick: () =>console.log('按鈕點(diǎn)擊'), // 事件監(jiān)聽(tīng)
'data-testid': 'submit-btn'// 自定義屬性
},
'提交表單'// 文本子節(jié)點(diǎn)
)
/* 等效模板:
<button
class="btn-primary"
@click="handleClick"
data-testid="submit-btn">
提交表單
</button>
*/
3.2 組件創(chuàng)建模式
// 創(chuàng)建帶props的組件
import CustomInput from './CustomInput.vue'
const inputField = h(CustomInput, {
modelValue: '默認(rèn)值',
'onUpdate:modelValue': (value) => {
console.log('值更新:', value)
}
})
/* 等效模板:
<CustomInput
v-model="value"
@update:modelValue="handleUpdate" />
*/
3.3 動(dòng)態(tài)子節(jié)點(diǎn)處理
// 生成列表項(xiàng)
const todoList = h('ul',
{ id: 'todo-list' },
todos.value.map((todo, index) =>
h('li', {
key: todo.id,
class: { completed: todo.done }
}, todo.text)
)
)
/* 等效模板:
<ul id="todo-list">
<li
v-for="todo in todos"
:key="todo.id"
:class="{ completed: todo.done }">
{{ todo.text }}
</li>
</ul>
*/
四、Vue3的顛覆性變化
4.1 與Vue2的render函數(shù)對(duì)比
// Vue2的Options API寫(xiě)法
exportdefault {
render(h) {
return h('div', {
attrs: { id: 'box' } // 屬性要放在attrs對(duì)象
}, [
h('span', 'Hello')
])
}
}
// Vue3的Composition API寫(xiě)法
import { h } from'vue'
exportdefault {
setup() {
return() => h('div', {
id: 'box'// 屬性平鋪
}, [
h('span', 'Hello')
])
}
}
4.2 性能優(yōu)化新特性
// 靜態(tài)節(jié)點(diǎn)提升(Compile-time優(yōu)化)
const staticNode = h('div', { class: 'logo' }, '靜態(tài)內(nèi)容')
export default {
setup() {
const dynamicData = ref(0)
return () => [
staticNode, // 復(fù)用靜態(tài)VNode
h('p', dynamicData.value) // 動(dòng)態(tài)節(jié)點(diǎn)
]
}
}
五、最佳實(shí)踐指南
5.1 合理選擇使用場(chǎng)景
推薦使用:
- ? 動(dòng)態(tài)生成組件類型(如:根據(jù)配置渲染不同組件)
- ? 需要精細(xì)控制渲染邏輯的場(chǎng)景
不推薦使用:
- ? 已有現(xiàn)成模板組件的場(chǎng)景
5.2 性能陷阱規(guī)避
// 錯(cuò)誤示例:每次渲染都創(chuàng)建新數(shù)組
function BadExample() {
return h('div',
[1, 2, 3].map(n => h('span', n)) // 每次都會(huì)生成新數(shù)組
)
}
// 正確優(yōu)化:緩存靜態(tài)部分
const staticItems = [1, 2, 3].map(n => h('span', n))
function GoodExample() {
return h('div', staticItems) // 復(fù)用靜態(tài)節(jié)點(diǎn)
}
5.3 與JSX的配合
// 配置JSX支持(vite.config.js)
import vue from'@vitejs/plugin-vue'
exportdefault {
plugins: [
vue({
jsx: true// 開(kāi)啟JSX支持
})
]
}
// JSX組件示例
exportdefault defineComponent({
setup() {
const count = ref(0)
return() => (
<div class="counter">
<button onClick={() => count.value++}>
{count.value}
</button>
</div>
)
}
})
閱讀原文:https://mp.weixin.qq.com/s/3NlCR7AlnVYpYu2Beb_JRg
該文章在 2025/4/14 10:59:07 編輯過(guò)