当前位置: 首页 > java >正文

手写 vue 源码 === runtime-core 实现


目录

1. 创建 runtime-core 包:平台无关的运行时核心

2. 虚拟节点(VNode)的实现

形状标识(ShapeFlags):高效的类型标记

createVNode:创建虚拟节点

h 函数:开发者友好的 VNode 创建器

3. createRenderer:渲染器的核心

4. 创建真实 DOM(浏览器平台实现)

5.优化调用方法 

总结:Vue3 运行时核心架构


1. 创建 runtime-core 包:平台无关的运行时核心

Vue3 的架构设计将平台相关代码和核心逻辑分离,runtime-core 包就是 Vue 的核心引擎,它不关心具体运行在浏览器、Node.js 还是其他环境。我们先创建它的基础结构:

// runtime-core/package.json
{"name": "@vue/runtime-core","module": "dist/runtime-core.esm-bundler.js","types": "dist/runtime-core.d.ts","files": ["index.js", "dist"],"buildOptions": {"name": "VueRuntimeCore","formats": ["esm-bundler", "cjs"]}
}

关键依赖

  • @vue/shared:公共工具函数

  • @vue/reactivity:响应式系统

pnpm install @vue/shared@workspace @vue/reactivity@workspace --filter @vue/runtime-core

为什么这样设计?
这种架构让 Vue 可以轻松适配不同平台。浏览器端使用 runtime-dom 提供 DOM 操作,小程序端则实现特定平台的渲染逻辑,共享同一核心。

2. 虚拟节点(VNode)的实现

形状标识(ShapeFlags):高效的类型标记

Vue 使用位运算高效组合节点类型,就像给节点贴多个标签:

export const enum ShapeFlags {ELEMENT = 1,                 // 0000000001 -> 普通元素FUNCTIONAL_COMPONENT = 1 << 1, // 0000000010 -> 函数式组件STATEFUL_COMPONENT = 1 << 2,  // 0000000100 -> 有状态组件TEXT_CHILDREN = 1 << 3,       // 0000001000 -> 文本子节点ARRAY_CHILDREN = 1 << 4,      // 0000010000 -> 数组子节点SLOTS_CHILDREN = 1 << 5,      // 0000100000 -> 插槽子节点// ... 其他类型COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT // 0000000110 -> 组件(两种组件的组合)
}

位运算妙用

  • | 组合类型:shapeFlag = ELEMENT | ARRAY_CHILDREN (1 + 16 = 17)

  • & 检查类型:if (shapeFlag & ShapeFlags.ELEMENT) 判断是否是元素

createVNode:创建虚拟节点
import { ShapeFlags, isString } from "@vue/shared";
export function isVNode(value: any) {return value ? value.__v_isVNode === true : false;
}
/***  固定的参数* @param type 类型* @param props 属性* @param children 子节点* @returns*/
export function createVNode(type, props, children = null) {const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0;const vnode = {__v_isVNode: true, //判断是否是虚拟节点type, //虚拟节点的类型props, //虚拟节点的属性children, //虚拟节点的子节点key: props && props["key"], //diff算法中需要的keyel: null, //虚拟节点对应的实际节点shapeFlag,};if (children) {let type = 0;// 如果shapeFlag为9 说明元素中包含一个文本// 如果shapeFlag为17 说明元素中有多个子节点if (Array.isArray(children)) {type = ShapeFlags.ARRAY_CHILDREN;} else {type = ShapeFlags.TEXT_CHILDREN;}vnode.shapeFlag |= type;}// 返回vnodereturn vnode;
}
h 函数:开发者友好的 VNode 创建器

h 函数是 createVNode 的语法糖,处理多种参数形式:

import { isObject } from "@vue/shared";
import { createVNode, isVNode } from "./createVNode";export function h(type, propsOrChildren?, children?) {const l = arguments.length;// 只有属性,或者一个元素儿子的时候if (l === 2) {//是对象不是数组 「h (h1,虚拟节点 | 属性)」if (isObject(propsOrChildren) && !Array.isArray(propsOrChildren)) {// 虚拟节点 「 h('div',h('span')) 」if (isVNode(propsOrChildren)) {return createVNode(type, null, [propsOrChildren]);} else {// 属性  h('div',{style:{color:'red'}});return createVNode(type, propsOrChildren);}}// 儿子纯文本 或者数组return createVNode(type, null, propsOrChildren);} else {if (l > 3) {// 超过3个除了前两个都是儿子children = Array.prototype.slice.call(arguments, 2);} else if (l === 3 && isVNode(children)) {children = [children]; // 儿子是元素将其包装成 h('div',null,[h('span')])}return createVNode(type, propsOrChildren, children); // h('div',null,'erxiao')}
}

使用示例

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head><div id="app">
</div><body><script type="module">// import {//     createRenderer,//     render,//     h// } from '/node_modules/@vue/runtime-dom/dist/runtime-dom.esm-browser.js'import {// renderOptions,h,render} from "./runtime-dom.js"/*** 参数可能是一个「类型」* 参数可以有两个「类型,属性/儿子」* 或者三个(有可能超过三个,从第三个开始都是儿子)* h(类型,属性,儿子)* h(类型,儿子)* */// 两个参数,第二个可能是属性,或者虚拟节点「__v_isVNode」const ele1 = h('div', { a: 1 })const ele2 = h('div', h('p'))// 第二个参数就是一个数组==>儿子const ele3 = h('div', [h('p'), h("div")])// 直接传递非对象的,文本const ele4 = h('div', 'hello')// 不能出现三个参数 第二个只能是属性const ele5 = h('iv', {}, 'hello')const ele6 = h('div', {}, h('p')) //虚拟节点 包装成数组// 如果超过是三个参数后边都是儿子const ele7 = h('div', {style: {color: 'red'}}, h('p', 'p'), h('div', 'div'))// 其他情况就是属性render(ele7, app)console.log(ele7);</script>
</body></html>

3. createRenderer:渲染器的核心

渲染器是 Vue 的核心大脑,连接虚拟节点和平台具体的 DOM 操作:

// core 中不关心如何渲染 {完全不关心api里面的,可以跨平台}
import { ShapeFlags } from "@vue/shared";
export function createRenderer(renderOptions) {const {insert: hostInsert, //  插入remove: hostRemove, // 移除createElement: hostCreateElement, // 创建元素createText: hostCreateText, // 创建文本setText: hostSetText, // 设置文本setElementText: hostSetElementText, // 设置元素文本parentNode: hostParentNode, // 获取父节点nextSibling: hostNextSibling, // 获取下一个兄弟节点patchProp: hostPatchProp, // 更新属性} = renderOptions;// 创建数组子节点const mountChildren = (children, container) => {for (let i = 0; i < children.length; i++) {// children[i] 可能是纯文本。。。patch(null, children[i], container);}};const mountElement = (vnode, container) => {// type:元素 props:属性 children:子节点const { type, props, children, shapeFlag } = vnode;const el = hostCreateElement(type);if (props) {for (const key in props) {// 更新属性 「元素 属性名 旧属性,新属性」hostPatchProp(el, key, null, props[key]);}}//文本if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {// 设置元素文本 「元素,文本」hostSetElementText(el, children);} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {// 数组mountChildren(children, el);}// 插入元素 「元素,容器」hostInsert(el, container);};/**** @param n1 旧节点* @param n2  新节点* @param container 容器* @returns*/// 渲染走这里,更新也走这里const patch = (n1, n2, container) => {if (n1 === n2) {// 相同节点,直接跳过return;}// 初始化if (n1 === null) {mountElement(n2, container);}};// 多次渲染,会进行虚拟节点的比对,进行更新const render = (vnode, container) => {patch(container._vnode || null, vnode, container);container._vnode = vnode;};return {render,};
}

4. 创建真实 DOM(浏览器平台实现)

在 runtime-dom 包中提供浏览器环境的 DOM 操作:

// runtime-dom/src/nodeOps 这里存放常见 DOM 操作 API,
// 不同运行时提供的具体实现不一样,最终将操作方法传递到runtime-core中,
// 所以runtime-core不需要关心平台相关代码~
export const nodeOps = {insert: (child, parent, anchor) => {// 添加节点parent.insertBefore(child, anchor || null);},remove: (child) => {// 节点删除const parent = child.parentNode;if (parent) {parent.removeChild(child);}},createElement: (tag) => document.createElement(tag), // 创建节点createText: (text) => document.createTextNode(text), // 创建文本setText: (node, text) => (node.nodeValue = text), //  设置文本节点内容setElementText: (el, text) => (el.textContent = text), // 设置文本元素中的内容parentNode: (node) => node.parentNode, // 父亲节点nextSibling: (node) => node.nextSibling, // 下一个节点querySelector: (selector) => document.querySelector(selector), // 搜索元素
};

5.优化调用方法 

export const render = (vnode, container) => {createRenderer(renderOptions).render(vnode, container);
};
export * from '@vue/runtime-core';

这样在页面中可以直接调用render方法进行渲染啦~ 

总结:Vue3 运行时核心架构

  1. 分层架构

    • runtime-core:平台无关的核心逻辑

    • runtime-dom:浏览器特定的 DOM 操作

    • reactivity:独立的响应式系统

  2. 虚拟节点(VNode)

    • 轻量级的 JS 对象描述 DOM

    • 使用 ShapeFlags 高效标识节点类型

    • h() 函数简化创建过程

  3. 渲染器(Renderer)

    • createRenderer 工厂函数接收平台操作

    • patch 函数处理初始化和更新

    • 递归处理子节点形成树形结构

  4. 跨平台能力

    • 通过抽象 DOM 操作接口

    • 同一核心适用于 Web、小程序、Native

通过实现 runtime-core,我们深入理解了 Vue3 的核心工作原理。这种设计不仅提高了代码复用性,还使得 Vue3 能够灵活适应各种渲染环境,为开发者提供一致的开发体验。

http://www.xdnf.cn/news/12787.html

相关文章:

  • RISC-V 开发板 + Ubuntu 23.04 部署 open_vins 过程
  • Kaggle注册不成功,添加插件header Editor
  • FreeRTOS同步和互斥
  • CppCon 2015 学习:Large Scale C++ With Modules
  • Codeforces Educational 179(ABCDE)
  • MyBatis中foreach集合用法详解
  • yyMMddHHSSS 是什么日期
  • 99. Java 继承(Inheritance)
  • 【Java学习笔记】日期类
  • Selenium4+Python的web自动化测试框架
  • STM32的DMA简介
  • 【面试题】如何保证MQ的消息不丢失、不重复
  • 免费批量PDF转Word工具
  • Java安全点safepoint
  • Java 企业项目中的线程管理策略
  • 四.抽象工厂模式
  • opencv学习笔记2:卷积、均值滤波、中值滤波
  • C语言指针与数组sizeof运算深度解析:从笔试题到内存原理
  • 数学建模期末速成 主成分分析的基本步骤
  • 什么是 Ansible 主机和组变量
  • 如何优化React Native应用以适配HarmonyOS5?
  • python打卡训练营打卡记录day48
  • VLM引导的矢量草图生成AutoSketch
  • 数据库入门:从原理到应用
  • Windows之官方Sysinternals工具集
  • ubuntu 系统分区注意事项
  • 36 C 语言内存操作函数详解:memset、memcpy、memccpy、memmove、memcmp、memchr
  • 开启二进制日志 MySQL显示关闭,关闭二进制日志 MySQL恢复正常
  • 全球人工智能技术大会(GAITC 2025):技术前沿与产业融合的深度交响
  • Prompt工程学习之思维树(TOT)