Vue 中的 `render` 函数

Vue 中的 render 函数

在 Vue 中,render 函数是编程式创建虚拟 DOM(VNode) 的核心方式,它替代了模板语法,直接通过 JavaScript 逻辑生成节点结构。模板语法最终也会被 Vue 编译为 render 函数,因此 render 是更底层、更灵活的实现方式。

核心概念

  • 作用:动态生成复杂 DOM 结构,处理模板难以覆盖的灵活逻辑(如动态标签、复杂条件/列表渲染)。
  • 对比模板
    • 模板:声明式,简洁易读,适合常规场景;
    • render:命令式,灵活度高,适合动态/复杂逻辑场景。
  • 核心函数h(createElement 的简写),用于创建 VNode,语法:h(tag, data, children)

一、Vue2 中的 render

Vue2 中 render 是组件选项,接收 createElement(简称 h)作为参数,返回 VNode。

1. 基本语法

js 复制代码
Vue.component('MyButton', {
  render(h) {
    // h(标签/组件, 节点属性, 子节点)
    return h(
      'button', // 1. 标签名/组件
      {
        // 2. 节点属性(class、style、事件、props 等)
        class: 'my-btn',
        attrs: { type: 'button' }, // 普通 HTML 属性
        on: { click: this.handleClick } // 事件绑定
      },
      '点击我' // 3. 子节点(字符串/VNode 数组)
    );
  },
  methods: {
    handleClick() {
      alert('Vue2 Render 点击');
    }
  }
});

2. 渲染组件

js 复制代码
import Child from './Child.vue';

export default {
  render(h) {
    return h(Child, {
      props: { msg: 'Hello Vue2' }, // 组件 props
      on: { change: this.handleChange } // 组件事件
    });
  },
  methods: {
    handleChange(val) {
      console.log(val);
    }
  }
};

二、Vue3 中的 render

Vue3 对 render 做了简化,h 函数需手动导入,且支持组合式 API(setup)中直接返回。

1. 选项式 API 用法

js 复制代码
import { h } from 'vue'; // 手动导入 h 函数

export default {
  render() {
    return h(
      'div',
      { class: 'container', onClick: () => console.log('click') },
      [h('h1', 'Vue3 Render'), h('p', '底层渲染方式')] // 子节点数组
    );
  }
};

2. 组合式 API(setup)用法

setup 可直接返回 render 函数(或 VNode),是 Vue3 推荐方式:

js 复制代码
import { h, ref } from 'vue';

export default {
  setup(props, { emit, slots }) {
    const count = ref(0);
    const handleClick = () => {
      count.value++;
      emit('update', count.value);
    };

    // 返回 render 函数(响应式数据会自动追踪)
    return () => h(
      'button',
      { onClick: handleClick },
      `点击了 ${count.value} 次`
    );
  }
};

3. h 函数参数详解(Vue3)

js 复制代码
h(
  // 1. 类型:标签名 | 组件 | 异步组件
  'div',
  // 2. 可选:props/属性(Vue3 用驼峰式事件名)
  {
    class: ['foo', { bar: true }], // 类名(数组/对象)
    style: { color: 'red' }, // 样式
    onClick: () => {}, // 事件(驼峰式)
    props: { msg: 'hello' }, // 组件 props
    attrs: { id: 'box' }, // 普通 HTML 属性(非 props)
    key: 'unique-key' // 列表 key
  },
  // 3. 可选:子节点(字符串 | VNode 数组 | 插槽)
  [
    h('span', '子节点1'),
    slots.default?.() // 默认插槽
  ]
);

三、常见场景示例

1. 动态标签/组件

根据参数渲染不同标签(替代模板的动态组件):

js 复制代码
// Vue3 setup
import { h } from 'vue';

export default {
  props: {
    tag: { type: String, default: 'div' },
    content: { type: String, default: '' }
  },
  setup(props) {
    return () => h(props.tag, null, props.content);
  }
};

2. 条件渲染(替代 v-if/v-else)

js 复制代码
import { h, ref } from 'vue';

export default {
  setup() {
    const isShow = ref(true);
    return () => isShow.value 
      ? h('div', '显示内容') 
      : h('p', '隐藏时的内容');
  }
};

3. 列表渲染(替代 v-for)

js 复制代码
import { h, ref } from 'vue';

export default {
  setup() {
    const list = ref(['Vue', 'React', 'Angular']);
    // 列表需手动加 key
    return () => h('ul', list.value.map(item => h('li', { key: item }, item)));
  }
};

4. 插槽处理

js 复制代码
// Vue3 setup 处理插槽
import { h } from 'vue';

export default {
  setup(props, { slots }) {
    return () => h('div', [
      slots.default?.(), // 默认插槽
      slots.scoped?.({ text: '作用域插槽内容' }) // 作用域插槽
    ]);
  }
};

四、JSX 与 render 的关系

Vue 支持 JSX 语法(需配置 @vitejs/plugin-vue-jsx 插件),JSX 最终会被编译为 h 函数调用,写法更接近 HTML:

jsx 复制代码
import { ref } from 'vue';

export default {
  setup() {
    const count = ref(0);
    return () => (
      <div class="container">
        <button onClick={() => count.value++}>
          点击 {count.value} 次
        </button>
        {[1,2,3].map(item => <li key={item}>{item}</li>)}
      </div>
    );
  }
};

上述 JSX 等价于:

js 复制代码
return () => h('div', { class: 'container' }, [
  h('button', { onClick: () => count.value++ }, `点击 ${count.value} 次`),
  [1,2,3].map(item => h('li', { key: item }, item))
]);

五、注意事项

  1. 优先级 :组件中同时存在 templaterender 时,render 优先级更高(覆盖模板)。
  2. 性能优化 :避免在 render 函数中每次创建新对象/数组(如 h 的第二个参数),可通过 ref/reactive 缓存。
  3. 事件绑定 :Vue2 用 on: { click: fn },Vue3 用 onClick: fn(驼峰式)。
  4. 列表 key :列表渲染时必须手动设置 key(放在 h 的第二个参数中),避免复用错误。

总结

场景 推荐方式
常规/简单结构 模板语法
动态/复杂逻辑 render 函数
想兼顾 HTML 可读性 JSX(编译为 h)

render 是 Vue 渲染的底层核心,适合处理模板难以实现的灵活逻辑;日常开发中,模板/JSX 更易维护,仅在必要时使用原生 render 函数。

Vue2 和 Vue3 中 render 函数的对比

Vue2 和 Vue3 中 render 函数的核心目标一致(生成 VNode),但因 Vue3 重构了响应式系统、引入组合式 API,并简化了底层 API 设计,两者在 h 函数使用、参数格式、插槽处理、结合方式 等方面存在显著差异。以下从核心维度拆解区别,并结合示例对比。

一、核心差异总览表

对比维度 Vue2 中的 render 函数 Vue3 中的 render 函数
h 函数(createElement) 作为 render 函数的第一个参数注入,无需导入 手动从 vue 包导入,render 无默认注入
书写位置/结合方式 仅支持选项式 API(render: function(h) {}),依赖 this 访问实例 支持选项式 API(少用)+ 组合式 API(setup 返回 render,推荐),setup 中无 this
事件绑定格式 嵌套在 on/nativeOn 对象中(on: { click: fn } 直接驼峰式平级绑定(onClick: fn),移除 nativeOn
属性绑定 props/attrs 需嵌套在对应字段(props: { msg: '' } 支持平级简写,attrs 仍保留(非 props 属性)
插槽处理 区分 this.$slots(普通插槽)/this.$scopedSlots(作用域插槽) 统一通过 slots 对象(setup 上下文),无需区分普通/作用域插槽
函数式组件 需声明 functional: true,render 接收 context 第二个参数 移除 functional 选项,直接返回 VNode 即可
this 指向 render 内 this 指向组件实例 选项式 API 中 this 仍指向实例;setup 返回的 render 无 this
响应式适配 依赖 this 上的响应式数据(Object.defineProperty) 闭包访问 setup 内的 ref/reactive(Proxy),追踪更高效

二、关键差异详细解析

1. h 函数的获取方式(最核心区别)

Vue2 中,h(createElement)是框架自动注入到 render 函数的第一个参数,无需手动导入;

Vue3 中,h 函数被抽离为独立导出的 API,必须手动导入后才能使用,且 render 函数不再接收 h 作为参数。

示例对比
js 复制代码
// Vue2:h 是 render 的参数,无需导入
Vue.component('MyBtn', {
  render(h) { // 框架注入 h
    return h('button', { on: { click: this.handleClick } }, '点击');
  },
  methods: { handleClick() {} }
});

// Vue3:手动导入 h,render 无参数(选项式 API)
import { h } from 'vue';
export default {
  render() { // 无注入的 h,需先导入
    return h('button', { onClick: this.handleClick }, '点击');
  },
  methods: { handleClick() {} }
};

// Vue3 推荐:setup 返回 render 函数(无 this)
import { h } from 'vue';
export default {
  setup() {
    const handleClick = () => {};
    return () => h('button', { onClick: handleClick }, '点击'); // 闭包使用 h
  }
};

2. 事件/属性绑定格式

Vue2 对事件、属性的绑定需嵌套在特定字段中,Vue3 简化了格式,更贴近原生 JS 写法,同时移除了冗余字段。

(1)事件绑定
  • Vue2:普通事件写在 on 对象,组件原生事件写在 nativeOn 对象;
  • Vue3:所有事件直接用驼峰式 平级绑定(如 onClick),移除 nativeOn(组件原生事件无需特殊区分)。
(2)属性绑定
  • Vue2:props 需写在 props 字段,普通 HTML 属性写在 attrs 字段;
  • Vue3:props 可平级简写(或仍放 props 字段),attrs 保留用于非 props 属性,结构更简洁。
示例对比
js 复制代码
// Vue2:事件/属性嵌套绑定
render(h) {
  return h(
    'MyComponent', // 自定义组件
    {
      props: { msg: 'Vue2' }, // 组件 props
      attrs: { id: 'btn' },   // 普通 HTML 属性
      on: { change: this.handleChange }, // 组件自定义事件
      nativeOn: { click: this.handleNativeClick } // 组件原生点击事件
    }
  );
}

// Vue3:平级绑定,移除 nativeOn
import { h } from 'vue';
export default {
  setup() {
    return () => h(
      'MyComponent',
      {
        props: { msg: 'Vue3' }, // 可选平级:msg: 'Vue3'
        attrs: { id: 'btn' },
        onChange: handleChange, // 自定义事件(驼峰)
        onClick: handleNativeClick // 原生事件(无需 nativeOn)
      }
    );
  }
};

3. 插槽处理方式

Vue2 区分「普通插槽」(this.$slots)和「作用域插槽」(this.$scopedSlots),使用繁琐;

Vue3 统一了插槽 API,通过 slots 对象(setup 上下文参数)访问所有插槽,无需区分普通/作用域,且无 this 依赖。

示例对比
js 复制代码
// Vue2:区分 $slots 和 $scopedSlots
render(h) {
  return h('div', [
    this.$slots.default(), // 普通默认插槽
    this.$scopedSlots.content({ val: 123 }) // 作用域插槽
  ]);
}

// Vue3:setup 中通过 slots 统一访问
import { h } from 'vue';
export default {
  setup(props, { slots }) { // 从上下文获取 slots
    return () => h('div', [
      slots.default?.(), // 默认插槽(可选链避免报错)
      slots.content?.({ val: 123 }) // 作用域插槽(无需区分)
    ]);
  }
};

4. 函数式组件的实现

Vue2 中函数式组件需显式声明 functional: true,且 render 函数接收 context 作为第二个参数;

Vue3 移除了 functional 选项,函数式组件可直接写成「返回 VNode 的函数」,或在 setup 中返回 render 函数,更轻量化。

示例对比
js 复制代码
// Vue2:函数式组件(需声明 functional)
Vue.component('FunctionalComp', {
  functional: true, // 必须声明
  render(h, context) { // 第二个参数是上下文(props/slots 等)
    return h('div', context.props.msg);
  },
  props: { msg: String }
});

// Vue3:函数式组件(无需声明 functional)
import { h } from 'vue';
// 方式1:直接导出函数
export const FunctionalComp = (props) => h('div', props.msg);
FunctionalComp.props = { msg: String };

// 方式2:setup 返回 render(更推荐)
export default {
  props: { msg: String },
  setup(props) {
    return () => h('div', props.msg);
  }
};

5. 响应式适配与 this 指向

  • Vue2:render 函数依赖 this 访问组件实例上的响应式数据(如 this.msg),响应式基于 Object.defineProperty,需通过 this 触发依赖收集;
  • Vue3:setup 返回的 render 函数通过闭包 直接访问 setup 内的 ref/reactive 数据,无 this 依赖,响应式基于 Proxy,追踪更高效、更灵活。
示例对比
js 复制代码
// Vue2:依赖 this 访问响应式数据
export default {
  data() {
    return { count: 0 };
  },
  render(h) {
    return h('button', { on: { click: () => this.count++ } }, `点击 ${this.count} 次`);
  }
};

// Vue3:闭包访问响应式数据(无 this)
import { h, ref } from 'vue';
export default {
  setup() {
    const count = ref(0); // 响应式数据
    return () => h(
      'button',
      { onClick: () => count.value++ }, // 直接访问 count
      `点击 ${count.value} 次`
    );
  }
};

三、其他细节差异

  1. 优先级 :两者均为 render 优先级高于 template,但 Vue3 中 setup 返回的 render 优先级最高;
  2. VNode 结构 :Vue3 的 VNode 优化了内部结构(如移除 data.staticClass 等冗余字段),但对外的 h 函数参数兼容大部分写法;
  3. 错误处理 :Vue3 对 render 函数的错误捕获更友好,结合 onErrorCaptured 可更精准处理渲染错误;
  4. TS 支持:Vue3 的 render 函数(尤其是 setup 写法)对 TypeScript 类型推导更完善,Vue2 需额外声明类型。

四、总结

Vue3 对 render 函数的改造核心是「轻量化、标准化、适配组合式 API」:

  • 移除框架注入的 h 函数,改为手动导入,降低耦合;
  • 简化事件/属性绑定格式,移除冗余字段(如 nativeOn);
  • 统一插槽 API,无需区分普通/作用域插槽;
  • 结合 setup 实现无 this 的写法,适配 Proxy 响应式系统;
  • 废弃 functional 选项,函数式组件更简洁。

日常开发中,Vue2 仍以选项式 API 的 render 为主,Vue3 推荐使用「setup 返回 render/JSX」的方式,仅在需要极致灵活度时使用原生 render 函数,模板/JSX 仍是更易维护的选择。

相关推荐
狮子座的男孩2 小时前
html+css基础:07、css2的复合选择器_伪类选择器(概念、动态伪类、结构伪类(核心)、否定伪类、UI伪类、目标伪类、语言伪类)及伪元素选择器
前端·css·经验分享·html·伪类选择器·伪元素选择器·结构伪类
听风吟丶2 小时前
Spring Boot 自动配置深度解析:原理、实战与源码追踪
前端·bootstrap·html
跟着珅聪学java2 小时前
HTML中设置<select>下拉框默认值的详细教程
开发语言·前端·javascript
IT_陈寒2 小时前
JavaScript 性能优化:5个被低估的V8引擎技巧让你的代码提速50%
前端·人工智能·后端
想睡好2 小时前
setup
前端·javascript·html
桜吹雪2 小时前
DeepSeekV3.2模型内置Agent体验
javascript·人工智能
逆向新手2 小时前
js逆向-某省特种设备aes加密研究
javascript·爬虫·python·逆向·js
光影少年2 小时前
react navite相比较传统开发有啥优势?
前端·react.js·前端框架
岁月宁静2 小时前
软件开发工程师如何借助 AI 工具进行软件自测
前端·ai编程·测试