Vue 渲染三剑客:createRenderer、h 和 render 详解

目录

一、核心渲染三件套

[二、h() 函数:虚拟节点创建器](#二、h() 函数:虚拟节点创建器)

基本用法

参数详解

虚拟节点(VNode)结构

为什么需要虚拟节点?

三、createRenderer():渲染器工厂

渲染器返回值

自定义渲染器示例

四、render():渲染执行函数

方法签名

工作流程

核心算法伪代码

五、三者的协同工作

完整流程图示

六、实际应用场景

[1. 自定义渲染器](#1. 自定义渲染器)

[1. createElement(type)](#1. createElement(type))

[2. patchProp(el, key, prev, next)](#2. patchProp(el, key, prev, next))

[3. insert(child, parent)](#3. insert(child, parent))

整体执行流程

[2. 动态内容更新](#2. 动态内容更新)

七、总结:渲染三剑客的关系


一、核心渲染三件套

在 Vue 的渲染系统中,有三个核心函数构成了渲染的基础:

复制代码
import { createRenderer, h, render } from 'vue';
    1. h() - 虚拟节点创建器
    1. createRenderer() - 渲染器工厂
    1. render() - 渲染执行函数

这三个函数协同工作,将声明式的组件描述转化为实际的 DOM 操作。下面我们逐一深入解析。

二、h() 函数:虚拟节点创建器

h() 是 Vue 中创建虚拟节点(Virtual DOM Node)的核心函数,名称源自 "hyperscript",意为 "生成 HTML 结构的脚本"

复制代码
// 创建简单元素
const vnode = h('div', 'Hello World');

// 创建带属性的元素
const vnode = h('button', {
  class: 'btn',
  onClick: () => console.log('Clicked!')
}, 'Submit');

// 创建嵌套结构
const vnode = h('div', [
  h('h1', 'Main Title'),
  h('p', 'Content paragraph')
]);

基本用法

复制代码
// 创建简单元素
const vnode = h('div', 'Hello World');

// 创建带属性的元素
const vnode = h('button', {
  class: 'btn',
  onClick: () => console.log('Clicked!')
}, 'Submit');

// 创建嵌套结构
const vnode = h('div', [
  h('h1', 'Main Title'),
  h('p', 'Content paragraph')
]);

参数详解

h() 函数有三种主要调用形式:

复制代码
// 1. 仅标签类型
h('div')

// 2. 标签 + 属性/子元素
h('div', { id: 'app' })
h('div', 'Hello')

// 3. 标签 + 属性 + 子元素
h('div', { class: 'container' }, [
  h('span', 'Item 1'),
  h('span', 'Item 2')
])

虚拟节点(VNode)结构

h() 返回的虚拟节点对象包含渲染所需的所有信息:

复制代码
{
  type: 'div',        // HTML标签或组件对象
  props: {            // 属性对象
    id: 'app',
    class: 'container'
  },
  children: [         // 子节点数组
    { type: 'p', children: 'Text content' },
    // ...其他子节点
  ],
  el: null,           // 对应的真实DOM(渲染后填充)
  key: undefined,     // 用于优化的key
  // ...其他内部属性
}

为什么需要虚拟节点?

  1. 跨平台能力:VNode 是平台无关的抽象表示

  2. 高效更新:通过比较新旧 VNode 树,最小化 DOM 操作

  3. 灵活的组件模型:支持函数式组件、异步组件等高级特性

  4. 服务端渲染:可在 Node.js 环境中生成 HTML 字符串

三、createRenderer():渲染器工厂

createRenderer() 是创建自定义渲染器的工厂函数,它接收一个包含平台特定操作的配置对象。

复制代码
const renderer = createRenderer({
  // 创建元素
  createElement(tag) {
    return document.createElement(tag);
  },
  
  // 设置元素文本
  setElementText(el, text) {
    el.textContent = text;
  },
  
  // 插入元素
  insert(child, parent, anchor = null) {
    parent.insertBefore(child, anchor);
  },
  
  // 属性比对更新
  patchProp(el, key, prevValue, nextValue) {
    // 处理class/style/event/attributes
  },
  
  // 其他操作...
});

渲染器返回值

createRenderer() 返回一个包含关键方法的对象:

复制代码
const {
  render,      // 渲染方法
  hydrate,     // 服务端渲染激活方法
  createApp    // 创建应用实例
} = renderer;

自定义渲染器示例

实现一个控制台渲染器:

复制代码
const consoleRenderer = createRenderer({
  createElement(tag) {
    return { tag };
  },
  
  insert(child, parent) {
    console.log(`添加 ${child.tag} 到 ${parent.tag}`);
  },
  
  setElementText(el, text) {
    console.log(`设置 ${el.tag} 文本: "${text}"`);
    el.text = text;
  }
});

const vnode = h('div', {}, [h('p', {}, 'Hello Console!')]);
consoleRenderer.render(vnode, { tag: 'root' });

输出:

复制代码
添加 p 到 div
设置 p 文本: "Hello Console!"
添加 div 到 root

四、render():渲染执行函数

render() 是实际执行渲染工作的方法,它将虚拟 DOM 转换为真实 DOM。

方法签名

复制代码
function render(vnode: VNode | null, container: HostElement): void;

工作流程

首次渲染:

复制代码
render(h('div', 'Hello'), document.getElementById('app'));
  1. 创建 <div> 元素

  2. 设置文本内容为 "Hello"

  3. 将元素插入到 app 容器中

更新渲染:

复制代码
// 第一次渲染
render(h('div', 'Initial'), container);

// 更新内容
render(h('div', 'Updated'), container);
  • 比较新旧 VNode

  • 仅更新变化的文本内容

卸载组件:

复制代码
// 渲染空内容
render(null, container);

核心算法伪代码

复制代码
function render(vnode, container) {
  if (vnode === null) {
    // 卸载逻辑
    if (container._vnode) {
      unmount(container._vnode);
    }
  } else {
    // 挂载或更新
    patch(container._vnode || null, vnode, container);
  }
  // 存储当前VNode引用
  container._vnode = vnode;
}

function patch(oldVNode, newVNode, container) {
  if (oldVNode === null) {
    // 首次挂载
    mount(newVNode, container);
  } else {
    // 更新逻辑
    if (shouldUpdate(oldVNode, newVNode)) {
      // 执行更新
      updateElement(oldVNode, newVNode);
    } else {
      // 完全替换
      unmount(oldVNode);
      mount(newVNode, container);
    }
  }
}

五、三者的协同工作

理解这三个函数如何协同工作至关重要:

设计阶段:

复制代码
// 1. 创建渲染器(平台相关)
const renderer = createRenderer({ /* DOM操作 */ });

// 2. 获取渲染函数
const { render } = renderer;

声明阶段:

复制代码
// 3. 使用h()创建虚拟节点
const vnode = h('div', { class: 'app' }, [
  h('h1', 'Hello Vue'),
  h('p', 'This is virtual DOM')
]);

执行阶段:

复制代码
// 4. 使用render()执行渲染
render(vnode, document.getElementById('app'));

完整流程图示

复制代码
  h() 创建
    ↓
虚拟节点树 (VNode Tree)
    ↓
render() 处理
    ↓
createRenderer 配置
    ↓
真实 DOM 操作
    ↓
浏览器显示

六、实际应用场景

1. 自定义渲染器

1. createElement(type)
  • 作用:创建虚拟 DOM 元素的基础对象

  • 参数:

    • type:元素类型(如 'circle')
  • 返回值 :返回一个包含 type 属性的基础对象(如 { type: 'circle' })

  • 执行时机 :在调用 h() 函数创建虚拟节点时触发

  • 示例:

    h('circle', ...) // 内部调用 createElement('circle')

2. patchProp(el, key, prev, next)
  • 作用:设置/更新虚拟 DOM 元素的属性

  • 参数:

    • el:虚拟元素对象(由 createElement 创建)

    • key:属性名(如 'position')

    • prev:旧属性值(首次创建时为 null)

    • next:新属性值

  • 执行时机:

    • 初始化时:为每个属性设置初始值

    • 更新时:当属性变化时更新值

      // 将 position 属性添加到 circle 元素
      patchProp(circleObj, 'position', null, [100, 100])

3. insert(child, parent)
  • 作用:将子元素插入父容器,并触发实际绘制

  • 参数:

    • child:子虚拟元素(如圆形对象)

    • parent:父容器(此处未直接使用)

  • 执行时机:当虚拟 DOM 的子节点被挂载到父节点时

  • 关键逻辑:

    if (child.type === 'circle') {
    draw(child); // 调用绘制函数
    }

整体执行流程
  1. 创建虚拟 DOM:

    复制代码
    const scene = h('canvas', {}, [
      h('circle', { position: [100, 100], radius: 50, color: 'blue' }),
      h('circle', { position: [200, 150], radius: 30, color: 'red' })
    ]);
    复制代码
  2. 渲染过程:

    • 遍历虚拟 DOM 树

    • 对每个 circle 元素:

      1. createElement('circle') → 创建基础对象 { type: 'circle' }

      2. patchProp() → 添加 position/radius/color 属性

      3. insert() → 触发 draw() 绘制

    • draw() → 使用 Canvas API 绘制圆形


复制代码
    const canvasElement = document.getElementById('canvas');
    const ctx = canvasElement.getContext('2d');

    const canvasRenderer = createRenderer({
        createElement(type) {
            return { type };
        },

        patchProp(el, key, prev, next) {
            el[key] = next;
        },

        insert(child, parent) {
            if (child.type === 'circle') {
                draw(child);
            }
        },


    });

    function draw(element) {
        const [x, y] = element.position || [0, 0];
        const radius = element.radius || 10;
        const color = element.color || 'black';

        ctx.beginPath();
        ctx.arc(x, y, radius, 0, Math.PI * 2);
        ctx.fillStyle = color;
        ctx.fill();
    }

    // 创建Canvas场景
    const scene = h('canvas', {}, [
        h('circle', { position: [100, 100], radius: 50, color: 'blue' }),
        h('circle', { position: [200, 150], radius: 30, color: 'red' })
    ]);

    canvasRenderer.render(scene, canvasElement);;

2. 动态内容更新

复制代码
let count = 0;
const container = document.getElementById('app');

function update() {
  // 创建新VNode
  const vnode = h('div', [
    h('h1', `Count: ${count}`),
    h('button', { onClick: () => {
      count++;
      update(); // 更新视图
    }}, 'Increment')
  ]);
  
  // 渲染更新
  render(vnode, container);
}

// 初始渲染
update();

七、总结:渲染三剑客的关系

函数 角色 输入 输出
h() 声明式构建界面 组件描述 虚拟节点(VNode)
createRenderer() 创建特定平台的渲染能力 平台DOM操作实现 渲染器对象
render() 执行渲染过程 VNode + DOM容器 实际UI更新

关键理解要点:

  1. h() 是声明式的:描述"应该是什么样子"

  2. createRenderer() 是平台适配层:决定"如何实现渲染"

  3. render() 是执行引擎:处理"何时以及怎样更新"

这种分层架构使得 Vue 能够:

  • 保持核心逻辑与平台无关

  • 轻松实现跨平台渲染

  • 提供高效的更新机制

  • 保持开发者友好的声明式 API

理解这三个核心函数的工作原理,是深入掌握 Vue 渲染机制和实现自定义渲染解决方案的关键基础。

相关推荐
陈随易2 小时前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
Dream-Y.ocean4 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Csvn5 小时前
性能监控
前端
Highcharts.js5 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn5 小时前
构建工具
前端
FRing035 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷6 小时前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen8 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒8 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫8 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库