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

手写 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 能够灵活适应各种渲染环境,为开发者提供一致的开发体验。

相关文章:

  • Mysql 基础
  • 第二十七章 位置参数
  • Pinocchio 库详解及其在足式机器人上的应用
  • FPGA静态功耗
  • 【FPGA开发】DDS信号发生器设计
  • 【动态规划】子序列问题(二)
  • Python安装使用教程
  • 国家奖学金答辩PPT+文稿
  • 第三章 内存
  • AUTOSAR实战教程--DoIP_01_配置项解释
  • 在VSCode中使用Ultralytics扩展
  • vscode 配置 latex
  • 港理工:LLM推理与推荐能力集成
  • 机器学习 [白板推导](四)[降维]
  • 计数排序_桶排序
  • hot100 -- 10.回溯系列
  • 电流舵DAC设计(二)
  • Vue-Leaflet地图组件开发(三)地图控件与高级样式设计
  • Python学习——排序
  • Java严格模式withResolverStyle解析日期错误及解决方案
  • 82端口做网站/个人网上卖货的平台
  • 博客网站怎么做/seo推广优化培训
  • 湛江电子商务网站建设/手机百度云电脑版入口
  • 怎么什么软件可以吧做网站/天津网站策划
  • 长春专业做网站的公司/网络推广营销技巧
  • 漳州正规网站建设价格/长尾关键词挖掘爱站工具