render函数是返回VNode的JavaScript函数,接收ctx和props参数,必须用h()创建VNode;它适用于动态结构、高阶逻辑封装等场景,与Composition API深度契合。

手动编写 render 函数,本质是用 JavaScript 直接创建 VNode(虚拟节点),绕过模板编译过程。Vue 3 中它更轻量、更函数式,且与 Composition API 天然契合——不是“替代模板的备选方案”,而是处理动态结构、封装高阶逻辑、构建 DSL 或优化极致性能时的必要工具。
理解 render 函数的核心输入与输出
render 是一个返回 VNode 的函数,接收两个隐式参数:ctx(组件上下文)和 props(标准化后的 props 对象)。你通常在 setup() 中定义它,并通过 defineComponent({ render }) 或直接导出函数方式使用。
关键点:
- VNode 不是真实 DOM,而是描述节点的普通 JS 对象(如
{ type: 'div', props: { id: 'app' }, children: [...] }) - 所有 VNode 创建必须调用
h()(即createVNode的别名),不能手写对象——否则会丢失响应性、patch 逻辑和类型校验 -
h()支持三种签名:h(type)、h(type, props)、h(type, props, children);children可为字符串、数组、其他 VNode,甚至为函数(用于作用域插槽)
从模板到 render:常见结构的手动映射
不必从零造轮子。把熟悉模板转成 render,是最高效的入门路径:
ZenHeart zenlink 与 zenlink-mcp 为 OpenClaw,涵盖安装、MCP 注册、钩子接线、入站轮询及升级维护。
立即学习“前端免费学习笔记(深入)”;
-
基础元素:
<button @click="handleClick" :class="btnClass">{{ text }}</button>→h('button', { onClick: handleClick, class: btnClass }, text) -
条件渲染:
v-if对应三元表达式或逻辑与:condition ? h('p', 'Yes') : null;v-show则用style: { display: condition ? 'block' : 'none' } -
列表渲染:
v-for对应list.map(item => h('li', { key: item.id }, item.name));务必设key,否则无法正确 diff -
插槽透传:默认插槽用
ctx.$slots.default?.(),命名插槽用ctx.$slots.header?.();作用域插槽需传参:ctx.$slots.item?.({ data: item })
实战高阶技巧:封装可复用的 VNode 工厂
render 函数真正的威力,在于将 UI 构建逻辑抽象为纯函数。例如封装一个带加载态、错误态、空数据态的“智能列表”:
(示例节选)
const SmartList = defineComponent({
props: ['items', 'loading', 'error'],
setup(props) {
const renderContent = () => {
if (props.loading) return h('div', '加载中...');
if (props.error) return h('div', { class: 'error' }, `错误:${props.error.message}`);
if (props.items?.length === 0) return h('div', '暂无数据');
return h('ul', props.items.map(item =>
h('li', { key: item.id }, item.title)
));
};
return () => h('section', { class: 'smart-list' }, renderContent());
}
});
这种模式天然支持细粒度响应式更新——只有 props.items 变化时,才重新执行 map 和生成 li VNode,其余状态变更只替换顶层占位节点。
避坑指南:容易忽略的关键细节
-
响应式丢失:不要在
h()外层解构响应式对象(如const { count } = toRefs(props)),再传进h()—— 应直接传props.count或用toRef(props, 'count') -
事件监听器命名:Vue 3 使用小驼峰(
onClick),不是 kebab-case(on-click);原生事件可用on:click形式,但推荐统一用onClick -
Fragment 支持:无需额外包裹根节点,直接返回数组即可:
return [h('h1'), h('p')],Vue 会自动创建 Fragment VNode -
调试困难:配合
console.log(compileTemplate('<template>...</template>'))查看编译后的 render 代码,是逆向学习的最佳途径

















