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

发布于:2025-06-08 ⋅ 阅读:(21) ⋅ 点赞:(0)

目录

一、核心渲染三件套

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

基本用法

参数详解

虚拟节点(VNode)结构

为什么需要虚拟节点?

三、createRenderer():渲染器工厂

渲染器返回值

自定义渲染器示例

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

方法签名

工作流程

核心算法伪代码

五、三者的协同工作

完整流程图示

六、实际应用场景

1. 自定义渲染器

1. createElement(type)

2. patchProp(el, key, prev, next)

3. insert(child, parent)

整体执行流程

2. 动态内容更新

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


一、核心渲染三件套

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

import { createRenderer, h, render } from 'vue';
  • 1. h() - 虚拟节点创建器
  • 2. createRenderer() - 渲染器工厂
  • 3. 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 渲染机制和实现自定义渲染解决方案的关键基础。


网站公告

今日签到

点亮在社区的每一天
去签到