Vue2 vs Vue3 的 h 函数:渲染函数完整迁移指南

Vue2 vs Vue3 的 h 函数:渲染函数完整迁移指南

概述

h()hyperscript(超脚本) 的缩写,用来以编程方式创建 VNode(虚拟节点) 。它是 Vue 渲染函数(Render Function)的核心 API,模板和 JSX 最终都会编译成 h() / createElement() 调用。

  • 🔹 Vue 2 :叫 createElement,由 render 函数的参数传入
  • 🔹 Vue 3 :叫 h,需要从 vue 中显式 import

📌 本文对照 Vue 官方最新文档(2026-07-09) 逐条校验,文末附官方强调的避坑清单。

━━━

一、为什么需要 h()?

模板写大部分 UI 很香,但遇到高度动态的结构(递归树、虚拟列表、动态组件组合)时,渲染函数的 JS 表达力更强。

核心心智模型始终不变:

scss 复制代码
h(type, props, children)
// 类型 + 属性 + 子节点 三件套

变的是属性对象的组织方式,以及事件/插槽的传递约定。

━━━

二、Vue 2 的 createElement

基本形态

javascript 复制代码
export default {
  render(createElement) {
    return createElement('div', { class: 'box' }, 'Hello')
  }
}

data 对象字段(重点)

javascript 复制代码
createElement('div', {
  class: { active: true },
  style: { color: 'red' },
  attrs: { id: 'foo' },                                   // 普通 HTML 属性
  props: { myProp: 'value' },                             // 组件 props
  domProps: { innerHTML: '<span>x</span>' },              // DOM 属性
  on: { click: this.handleClick },                        // 事件(组件 + 原生)
  nativeOn: { click: this.handleNativeClick },            // ⚠️ 仅组件,监听根元素原生事件
  directives: [{ name: 'show', value: true }],
  scopedSlots: {                                          // 作用域插槽
    default: (props) => createElement('span', props.text)
  },
  slot: 'header',                                         // 普通插槽
  key: 'unique-key',
  ref: 'myRef',
  refInFor: true
})

⚠️ 子节点限制

javascript 复制代码
// Vue 2 ❌ 数字会被忽略
createElement('div', [1, 2])

━━━

三、Vue 3 的 h()

基本形态

javascript 复制代码
import { h } from 'vue'

export default {
  render() {
    return h('div', { class: 'box' }, 'Hello')
  }
}

与 Vue 2 的关键区别

能力 Vue 2 Vue 3
children 类型 字符串 / VNode / 数组 + 数字 / 布尔 / 函数
attrs/props/domProps 分字段 统一平铺
事件 on / nativeOn 分开 合并为 onXxx
插槽 data.scopedSlots 第三个参数 children 对象
多根节点 ✅ Fragment

props 平铺 + 官方修饰符前缀

javascript 复制代码
import { h } from 'vue'

h('div', {
  class: { active: true },
  style: { color: 'red' },
  id: 'foo',                 // 普通属性
  myProp: 'value',           // 组件 props
  onClick: () => {},         // 事件统一 onXxx
  innerHTML: '<span>x</span>'// DOM 属性
})

💡 官方细节 :Vue 会自动判断该用 setAttribute 还是赋值 DOM property。也可手动指定:

  • .name → 作为 DOM property 赋值
  • ^width → 作为 HTML attribute 赋值
javascript 复制代码
h('div', { '.name': 'some-name', '^width': '100' })

━━━

四、事件处理的变化(迁移最容易踩坑)

Vue 3 移除了 nativeOn.native

javascript 复制代码
// Vue 2
createElement(MyComponent, {
  on: { close: fn },
  nativeOn: { click: fn }   // 监听根元素原生 click
})

// Vue 3
h(MyComponent, {
  onClose: fn,              // 组件 emits 的 close
  onClick: fn               // 未声明 emits 时透传到根元素
})

事件修饰符两种写法

.passive / .capture / .once ------ camelCase 拼接:

javascript 复制代码
h('input', {
  onClickCapture() {},
  onKeyupOnce() {}
})

② 其余修饰符 ------ 用 withModifiers

javascript 复制代码
import { h, withModifiers } from 'vue'
h('div', { onClick: withModifiers(() => {}, ['self', 'prevent']) })

⚠️ Vue 2 用前缀符号:'!click'(capture)、'~keyup'(once)、'~!mouseover'。迁移时要改写!

━━━

五、作用域插槽

Vue 2(写在 data 里)

javascript 复制代码
createElement(MyComponent, {
  scopedSlots: {
    default: (props) => createElement('span', props.text)
  }
})

Vue 3(写在第三个参数)

javascript 复制代码
import { h } from 'vue'

h(MyComponent, null, {        // ⚠️ 无 props 时传 null,避免被当 props
  default: (props) => h('span', props.text),
  header: () => h('h1', '标题')
})

子组件调用:

javascript 复制代码
// 子组件内
setup(props, { slots }) {
  return () => h('div', null, slots.default({ text: 'hi' }))
}

━━━

六、函数式组件

Vue 2

javascript 复制代码
export default {
  functional: true,
  render(createElement, context) {
    return createElement('div', context.children)
  }
}
// context 含: props / children / slots / scopedSlots / data / parent / listeners / injections

Vue 3(取消 functional: true)

javascript 复制代码
import { h } from 'vue'

const FunctionalComp = (props, { slots, attrs, emit }) => {
  return h('div', attrs, slots.default?.())
}
FunctionalComp.props = ['msg']

📌 Vue 3 的 context 只含 attrs / emit / slots ,无 this、无状态、无生命周期,渲染成本更低。

━━━

七、Fragment / Teleport / 自定义指令

Fragment 多根节点

javascript 复制代码
import { h, Fragment } from 'vue'
h(Fragment, [h('div', 'a'), h('div', 'b')])

Teleport

javascript 复制代码
import { h, Teleport } from 'vue'
h(Teleport, { to: '#modal' }, () => h('div', '弹窗'))

自定义指令 withDirectives

javascript 复制代码
import { h, withDirectives } from 'vue'
const pin = { mounted() {}, updated() {} }
const vnode = withDirectives(h('div'), [[pin, 200, 'top', { animate: true }]])

v-model 手动展开

javascript 复制代码
h(SomeComponent, {
  modelValue: props.modelValue,
  'onUpdate:modelValue': (v) => emit('update:modelValue', v)
})

━━━

八、template ref 版本差异

javascript 复制代码
// Vue 3.5+ 推荐 useTemplateRef
import { h, useTemplateRef } from 'vue'
setup() {
  const divEl = useTemplateRef('my-div')
  return () => h('div', { ref: 'my-div' })
}

// Vue 3.5 之前:直接传 ref() 对象
const divEl = ref()
return () => h('div', { ref: divEl })

━━━

九、VNode 必须唯一(官方强调约束)

javascript 复制代码
// ❌ 同一个 vnode 对象不能复用
const p = h('p', 'hi')
return h('div', [p, p])

// ✅ 用工厂函数
return h('div', Array.from({ length: 20 }).map(() => h('p', 'hi')))

━━━

十、完整差异速查表

概念 Vue 2 Vue 3
名称 createElement h(= createVNode
引入 参数传入 显式 import
data 结构 attrs/props/domProps/on/nativeOn 分字段 统一平铺
插槽 scopedSlots children 对象
函数式组件 functional: true 普通函数
多根 ✅ Fragment
性能优化 无 patchFlag patchFlag / shapeFlag

━━━

总结

渲染函数迁移的核心就是三件事:

  1. 🔧 data 拆字段 → props 平铺
  2. 🔧 on/nativeOn → onXxx
  3. 🔧 scopedSlots → 第三个参数 children 对象

记住 h(type, props, children) 三件套,剩下的只是约定变化。

💡 Vue 3.4 起不再隐式注册全局 JSX 命名空间,用 TSX 需在 tsconfig 配 jsxImportSource: "vue",且 Vue 的 JSX ≠ React 的 JSX。


本文对照 Vue 官方文档校验,如有疏漏欢迎评论指正。如果对你有帮助,点赞 👍 收藏 ⭐ 关注 🚀,后续继续分享 Vue3 进阶内容。

相关推荐
星栈1 小时前
LiveView 的认证系统:从登录到权限,我一开始以为有 `current_user` 就算完事了
前端·前端框架·elixir
在因斯坦1 小时前
摸摸鱼之前端自己研究 Jenkins 自动化部署
前端
snpgroupcn2 小时前
SAP实施风险管控注意事项:四大风险域与防范策略
前端·数据库·人工智能
阿成学长_Cain2 小时前
Linux talk 命令详解:经典终端实时聊天工具,无需图形界面即可对话
linux·运维·前端
lichenyang4532 小时前
从零开始写 HarmonyOS Web 容器:先让 ArkWeb 加载本地 H5
前端
kyriewen3 小时前
一个看不见的撇号,让Claude Code追踪了中国开发者三个月
前端·ai编程·claude
莫生灬灬3 小时前
DY联系人聊天Web面板支持获取联系人/聊天记录/发送消息
前端·chrome·websocket·c#
郑州光合科技余经理3 小时前
海外外卖平台源码改造实战——多语言核心代码实现
java·开发语言·前端·后端·mysql·架构·php
一tiao咸鱼3 小时前
前端转 agent # 02 - FastAPI 框架入门与原理
前端·python