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 渲染机制和实现自定义渲染解决方案的关键基础。

相关推荐
去旅行、在路上1 分钟前
chrome使用手机调试触屏web
前端·chrome
Aphasia31130 分钟前
模式验证库——zod
前端·react.js
lexiangqicheng1 小时前
es6+和css3新增的特性有哪些
前端·es6·css3
拉不动的猪2 小时前
都25年啦,还有谁分不清双向绑定原理,响应式原理、v-model实现原理
前端·javascript·vue.js
烛阴2 小时前
Python枚举类Enum超详细入门与进阶全攻略
前端·python
孟孟~2 小时前
npm run dev 报错:Error: error:0308010C:digital envelope routines::unsupported
前端·npm·node.js
孟孟~2 小时前
npm install 报错:npm error: ...node_modules\deasync npm error command failed
前端·npm·node.js
狂炫一碗大米饭2 小时前
一文打通TypeScript 泛型
前端·javascript·typescript
wh_xia_jun2 小时前
在 Spring Boot 中使用 JSP
java·前端·spring boot
二十雨辰3 小时前
[HTML5]快速掌握canvas
前端·html