一、为什么需要它们?从模板到虚拟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 编辑过