一、為什么需要它們?從模板到虛擬DOM的進化史
1.1 模板的局限(Vue2時代)
// 傳統模板編譯流程
<template>
<div>{{ message }}</div>
</template>
↓ 編譯 ↓
function render() {
return _c('div', [_v(_s(message))])
}
痛點:
- 動態組件需要
<component :is>
特殊語法
1.2 虛擬DOM的革命性
interface VNode {
type: string | Component // 元素類型
props: Record<string, any> // 屬性
children: VNode[] | string // 子節點
key?: string | number // 優化標識
// ...其他內部屬性
}
設計哲學:
- JavaScript對象描述DOM結構(輕量級"藍圖")
- 實現跨平臺渲染能力(Web/小程序/Canvas)
二、h函數與createVNode的關系解密
2.1 官方定義對比
// 源碼中的真實定義(vue-next/packages/runtime-core/src/vnode.ts)
export const createVNode = (
__DEV__ ? createVNodeWithArgsTransform : _createVNode
) as typeof _createVNode
// h函數是createVNode的類型重載版本
export function h(type: any, props?: any, children?: any): VNode {
// ...處理不同參數情況
return createVNode(type, props, children)
}
三、核心用法詳解(帶場景化示例)
3.1 基礎元素創建
// 創建帶事件監聽的按鈕
const button = h(
'button', // 元素類型
{
class: 'btn-primary', // class綁定
onClick: () =>console.log('按鈕點擊'), // 事件監聽
'data-testid': 'submit-btn'// 自定義屬性
},
'提交表單'// 文本子節點
)
/* 等效模板:
<button
class="btn-primary"
@click="handleClick"
data-testid="submit-btn">
提交表單
</button>
*/
3.2 組件創建模式
// 創建帶props的組件
import CustomInput from './CustomInput.vue'
const inputField = h(CustomInput, {
modelValue: '默認值',
'onUpdate:modelValue': (value) => {
console.log('值更新:', value)
}
})
/* 等效模板:
<CustomInput
v-model="value"
@update:modelValue="handleUpdate" />
*/
3.3 動態子節點處理
// 生成列表項
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函數對比
// Vue2的Options API寫法
exportdefault {
render(h) {
return h('div', {
attrs: { id: 'box' } // 屬性要放在attrs對象
}, [
h('span', 'Hello')
])
}
}
// Vue3的Composition API寫法
import { h } from'vue'
exportdefault {
setup() {
return() => h('div', {
id: 'box'// 屬性平鋪
}, [
h('span', 'Hello')
])
}
}
4.2 性能優化新特性
// 靜態節點提升(Compile-time優化)
const staticNode = h('div', { class: 'logo' }, '靜態內容')
export default {
setup() {
const dynamicData = ref(0)
return () => [
staticNode, // 復用靜態VNode
h('p', dynamicData.value) // 動態節點
]
}
}
五、最佳實踐指南
5.1 合理選擇使用場景
推薦使用:
不推薦使用:
5.2 性能陷阱規避
// 錯誤示例:每次渲染都創建新數組
function BadExample() {
return h('div',
[1, 2, 3].map(n => h('span', n)) // 每次都會生成新數組
)
}
// 正確優化:緩存靜態部分
const staticItems = [1, 2, 3].map(n => h('span', n))
function GoodExample() {
return h('div', staticItems) // 復用靜態節點
}
5.3 與JSX的配合
// 配置JSX支持(vite.config.js)
import vue from'@vitejs/plugin-vue'
exportdefault {
plugins: [
vue({
jsx: true// 開啟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 編輯過