vue3模块创建runtime-dom源码解析

 更新时间:2023年01月12日 10:30:51   作者:驺虞  
这篇文章主要为大家介绍了vue3模块创建runtime-dom源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

前言

runtime-dom 是针对浏览器的运行时,包括 DOM 操作、props(例如class、事件、样式以及其它attributes)的更新等内容;本小节我们开启 runtime-dom 的篇章。

创建模块

packages/runtime-dom/ 目录下创建目录文件:

│  │  └─ src
│  │     ├─ index.ts
│  │     ├─ modules
│  │     │  ├─ attr.ts  // attributes 的更新方法
│  │     │  ├─ class.ts // class 的更新
│  │     │  ├─ event.ts // 事件绑定的更新
│  │     │  └─ style.ts // style属性的更新
│  │     ├─ nodeOps.ts  // dom操作方法
│  │     └─ patchProp.ts    // 属性更新操作

创建 runtime-dom/package.json 文件:

{
  "name": "@vue/runtime-dom",
  "version": "1.0.0",
  "main": "index.js",
  "module": "dist/runtime-dom.esm-bundler.js",
  "unpkg": "dist/runtime-dom.global.js",
  "buildOptions": {
    "name": "VueRuntimeDOM",
    "formats": [
      "esm-bundler",
      "cjs",
      "global"
    ]
  }
}

nodeOptions

先创建一些操作 DOM 的方法,例如元素和文本的增删改查:

// runtime-dom/src/nodeOps.ts
export const nodeOps = {
  // 1. 创建元素
  createElement(tagName) {
    return document.createElement(tagName);
  },
  // 创建文本节点
  createText(text) {
    return document.createTextNode(text);
  },
  // 2. 插入元素
  insert(child, parent, anchor) {
    // 元素移动;
    // 当第二个参数为null时,插入到末尾;
    parent.insertBefore(child, anchor || null);
  },
  // 3. 移除元素
  remove(child) {
    const parent = child.parentNode;
    if (parent) {
      parent.removeChild(child);
    }
  },
  // 4. 查询元素
  querySelector(selector) {
    return document.querySelector(selector);
  },
  parentNode(node) {
    return node.parentNode;
  },
  nextSibling(node) {
    return node.nextSibling;
  },
  // 5. 设置文本内容
  setElementText(el, text) {
    el.textContent = text;
  },
  setText(node, text) {
    node.nodeValue = text;
  },
};

patchProps

patchProp

再来实现一些属性的更新方法:

// runtime-dom/src/patchProp.ts
import { patchAttr } from "./modules/attr";
import { patchClass } from "./modules/class";
import { patchEvent } from "./modules/event";
import { patchStyle } from "./modules/style";
export const patchProp = (el, key, prevValue, nextValue) => {
  if (key === "class") {
    // 1. class 类名
    patchClass(el, nextValue);
  } else if (key === "style") {
    // 2. 样式
    patchStyle(el, prevValue, nextValue);
  } else if (/^on[^a-z]/.test(key)) {
    // 3. onXxx 事件
    patchEvent(el, key, nextValue);
  } else {
    // 4. 其它 attributes 属性
    patchAttr(el, key, nextValue);
  }
};

我们将 props 分成四种类型:classstyleonXxx 事件、以及其它 attributes 属性;分别用四种方法来处理这四种 prop

patchClass

更新 class 属性:

  • value 为空时,使用 el.removeAttribute("class") 去掉 class 属性;
  • 否则设置元素的 className 属性。
// runtime-dom/src/modules/class.ts
export const patchClass = (el, value) => {
  if (value == null) {
    el.removeAttribute("class");
  } else {
    el.className = value;
  }
};

patchStyle

更新 style 属性:

  • style 没有新值时,去掉 style 属性;style 有新值时才进行更新;
  • 将新的样式添加到 style 上,如果老的 style 中有重复的,则直接将老的样式覆盖
  • 对于老的 style 中有、新的 style 中没有的样式,需要将其移除
// runtime-dom/src/modules/style.ts
export const patchStyle = (el, prev, next) => {
  if (next) {
    const style = el.style;
    // 1. 将新的样式添加到style上,如果有重复的直接覆盖
    for (let key in next) {
      style[key] = next[key];
    }
    for (let key in prev) {
      // 2. 老的有,新的没有,要移除掉
      if (next[key] == null) {
        style[key] = null;
      }
    }
  } else {
    el.removeAttribute("style");
  }
};

patchEvent

更新事件(事件是以 onXxx 的形式绑定的):

  • 通过一个调用器 invoker 来存储事件的回调函数(存储到invoker.value上),以及执行回调(执行invoker.value()
  • 需要将老事件缓存起来,缓存到 el._vei 属性上(缓存的是 invoker
  • 情况一:如果存在新的事件回调函数,且在 el._vei 中存在该事件的缓存,则是更新事件;直接更新 invoker.value 即可
  • 情况二:如果存在新的事件回调函数,但缓存中不存在,则是绑定新的事件;先创建 invoker,并将invoker 缓存到 el._vei 中,然后通过 el.addEventListener() 绑定事件
  • 情况三:如果不存在新的事件回调函数,则是移除事件,直接使用 el.removeEventListener() 将该事件移除。
// runtime-dom/src/modules/event.ts
function createInvoker(initialValue) {
  const invoker = (e) => invoker.value(e);
  // 将事件的回调绑定到 invoker.value 上,后续更新事件回调的时候,只需更新 invoker.value 即可
  invoker.value = initialValue;
  return invoker;
}
export const patchEvent = (el, key, nextValue) => {
  const invokers = el._vei || (el._vei = {}); // _vei 是 vue event invoker 的缩写
  const name = key.slice(2).toLowerCase(); // 获取事件名
  const existingInvoker = invokers[name]; // 取缓存
  // 1. 如果是更新事件的回调
  if (nextValue && existingInvoker) {
    // 事件是存储到 invoker.value 上的,更新该属性即可
    existingInvoker.value = nextValue;
  } else {
    // 2. 如果是绑定新的事件
    if (nextValue) {
      // 2.1 创建 invoker(事件的回调函数),并缓存起来(本质是缓存到el._vei上)
      const invoker = (invokers[name] = createInvoker(nextValue));
      // 2.2 绑定事件
      el.addEventListener(name, invoker);
    } else {
      // 3. 如果是移除事件
      // 3.1 解绑事件
      el.removeEventListener(name, existingInvoker);
      // 3.2 清除缓存
      invokers[name] = null;
    }
  }
};

patchAttr

更新其它 attributes

  • 使用 el.removeAttribute() 移除属性;
  • 使用 el.setAttribute() 新增和更新属性
// runtime-dom/src/modules/attr.ts
export const patchAttr = (el, key, value) => {
  if (value == null) {
    el.removeAttribute(key);
  } else {
    el.setAttribute(key, value);
  }
};

总结

本小节我们大致介绍了 runtime-dom 模块,实现了一些 DOM 操作和 props 更新方法; 下一小节,我们将介绍 runtime-core 模块相关的内容。

以上就是vue3模块创建runtime-dom源码解析的详细内容,更多关于vue3 runtime-dom模块创建的资料请关注脚本之家其它相关文章!

相关文章

  • Vue学习之axios的使用方法实例分析

    Vue学习之axios的使用方法实例分析

    这篇文章主要介绍了Vue学习之axios的使用方法,结合实例形式分析了vue.js axios库的功能及网络请求相关操作技巧,需要的朋友可以参考下
    2020-01-01
  • vue使用监听实现全选反选功能

    vue使用监听实现全选反选功能

    最近做的项目用到了全选全不选功能,于是我就自己动手写了一个,基于vue使用监听实现全选反选功能,具体实例代码大家参考下本文
    2018-07-07
  • 使用vue3+vite导入图片路径错乱问题排查及解决

    使用vue3+vite导入图片路径错乱问题排查及解决

    使用vue3+vite开发的时候,导入svg图片时,同一个文件夹下的文件,其中一个路径正常解析,另一个不行,更改文件名之后,该图片文件就可以正常解析了,本文给大家介绍了使用vue3+vite导入图片路径错乱问题排查及解决,需要的朋友可以参考下
    2024-03-03
  • vue3 diff 算法示例

    vue3 diff 算法示例

    这篇文章主要为大家介绍了vue3 diff 的算法示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07
  • vue3.x项目降级到vue2.7的解决方案

    vue3.x项目降级到vue2.7的解决方案

    Vue2.7是Vue2.x的最终次要版本,下面这篇文章主要给大家介绍了关于vue3.x项目降级到vue2.7的解决方案,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2023-03-03
  • 基于Vue实例对象的数据选项

    基于Vue实例对象的数据选项

    下面小编就为大家带来一篇基于Vue实例对象的数据选项。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • el-table 树形数据 tree-props 多层级使用避坑

    el-table 树形数据 tree-props 多层级使用避坑

    本文主要介绍了el-table 树形数据 tree-props 多层级使用避坑,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-08-08
  • Vue滚动页面到指定位置的实现及避坑

    Vue滚动页面到指定位置的实现及避坑

    这篇文章主要介绍了Vue滚动页面到指定位置的实现及避坑,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • Vue封装localStorage设置过期时间的示例详解

    Vue封装localStorage设置过期时间的示例详解

    这篇文章主要介绍了Vue封装localStorage设置过期时间的相关资料,在这个示例中,我们在MyComponent.vue组件的setup函数中导入了setItemWithExpiry和getItemWithExpiry函数,并在函数内部使用它们来设置和获取带有过期时间的localStorage数据,需要的朋友可以参考下
    2024-06-06
  • Vue中@click.stop与@click.prevent、@click.native使用

    Vue中@click.stop与@click.prevent、@click.native使用

    这篇文章主要介绍了Vue中@click.stop与@click.prevent、@click.native使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-08-08

最新评论