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

目录
  • 前言
  • 创建模块
  • nodeOptions
  • patchProps
    • patchProp
    • patchClass
    • patchStyle
    • patchEvent
    • patchAttr
  • 总结

前言

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模块创建的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue-next/runtime-core 源码阅读指南详解

    写在前面 最近又抽时间把 vue-next/runtime-core 的源码陆陆续续地看完了,期间整理了很多笔记,但都是碎片化的.本来是想整理一下,写成一篇文章分享出来的,但是感觉最终的成果物只能是一篇篇幅巨长的解析文,就算我一行一行的把源码加上注释,其阅读体验也会很差,因为每个人读代码的习惯不同,思路不同.正所谓抛砖引玉,所以,我觉的写一篇向导文作为这块砖应该是足够了,希望可以帮助到想看源码但觉得无从看起.无从下手的读者. 另一方面,也算是给自己挖一个坑,因为这篇文章中涉及到的很多内容,三言两

  • Vue.js和Vue.runtime.js区别浅析

    目录 区别 HTML Compiler template render() codesandbox.io 区别   Vue.js Vue.runtime.js 体积 最大 比Vue.js小40% 功能 包含HTML Compiler 不含HTML Compiler cdn引入 选择Vue.js 选择Vue.runtime.js 可见两者最大的区别就是:是否包含HTML Compiler HTML Compiler HTML Compiler 顾名思义是编译 HTML 的工具.在Vue中,页面元素

  • Vue完整版和runtime版的区别详解

    目录 创建Vue实例的三种方式 从HTML得到视图 用JS构建视图 使用vue-loader 两者对比 最佳实践 SEO友好 创建Vue实例的三种方式 从HTML得到视图 前提:使用完整版,CDN引入或者修改配置 const { defineConfig } = require('@vue/cli-service') module.exports = defineConfig({ // ... configureWebpack: { resolve: { alias: { vue$: 'vue/

  • Netty组件NioEventLoopGroup创建线程执行器源码解析

    目录 通过上一章的学习, 我们了解了Server启动的大致流程, 有很多组件与模块并没有细讲, 从这个章开始, 我们开始详细剖析netty的各个组件, 并结合启动流程, 将这些组件的使用场景及流程进行一个详细的说明 这一章主要学习NioEventLoop相关的知识, 何为NioEventLoop? NioEventLoop是netty的一个线程, 在上一节我们创建两个NioEventLoopGroup: EventLoopGroup bossGroup = new NioEventLoopGro

  • Vue $nextTick 为什么能获取到最新Dom源码解析

    目录 正文 修改数据之后 update 方法 nextTick 方法里面怎么执行传进去更新方法 正文 <template> <p id='text'>{{text}}</p> <button @click='change'>click</button> </template> <script> export default { data() { return { text: 'hello world' } } method

  • .Net Core中ObjectPool的使用与源码解析

    一.对象池 运用对象池化技术可以显著地提升性能,尤其是当对象的初始化过程代价较大或者频率较高.下面是ObjectPool源码中涉及的几个类.当你看过.Net Core源码很多时,你会发现,微软的开发很多都是这种模式,通过Policy构建Provider,通过Provider创建最终的类. 二.使用 这个组件的目的主要是将对象保存到对象池,用的时候直接去取,不需要重新创建,实现对象的重复利用.但是有个问题,假如对象池中开始没有对象或者取得数量大于对象池中的数量怎么办?在对象池中对象的数量不足时,此

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

    目录 前言 创建模块 nodeOptions patchProps patchProp patchClass patchStyle patchEvent patchAttr 总结 前言 runtime-dom 是针对浏览器的运行时,包括 DOM 操作.props(例如class.事件.样式以及其它attributes)的更新等内容:本小节我们开启 runtime-dom 的篇章. 创建模块 在 packages/runtime-dom/ 目录下创建目录文件: │ │ └─ src │ │ ├─

  • Vue3 编译流程-源码解析

    前言: Vue3 发布已经很长一段时间了,最近也有机会在公司项目中用上了 Vue3 + TypeScript + Vite 的技术栈,所以闲暇之余抽空也在抽空阅读 Vue3 的源码.本着好记性不如烂笔头的想法,在阅读源码时顺便记录了一些笔记,也希望能争取写一些源码阅读笔记,帮助每个想看源码但可能存在困难的同学减少理解成本. Vue2.x 的源码我也有过一些简单的阅读,自 Vue3 重构后,Vue 项目的目录结构也发生了很大的变化,各个功能模块被分别放入了 packages 目录下,职责更加清晰,

  • vue3 keepalive源码解析解决线上问题

    目录 引言 1.keepalive功能 2.keepalive使用场景 3.在项目中的使用过程 4.vue3 keepalive源码调试 5.vue3 keealive源码粗浅分析 6.总结 引言 1.通过本文可以了解到vue3 keepalive功能 2.通过本文可以了解到vue3 keepalive使用场景 3.通过本文可以学习到vue3 keepalive真实的使用过程 4.通过本文可以学习vue3 keepalive源码调试 5.通过本文可以学习到vue3 keepalive源码的精简分

  • Django drf请求模块源码解析

    DRF 框架,全称为 Django Rest Framework,是 Django 内置模块的扩展,用于创建标准化 RESTful API:它利用 ORM 映射数据库,并自定义序列化数据进行返回,多用于前后端分离项目 项目地址: https://github.com/encode/django-rest-framework 请求模块:request对象 源码入口 APIView类中dispatch方法中的:request=self.iniialize_request(*args, **kwarg

  • Vue3 AST解析器-源码解析

    目录 1.生成 AST 抽象语法树 2.创建 AST 的根节点 3.解析子节点 4.解析模板元素 Element 5.示例:模板元素解析 上一篇文章Vue3 编译流程-源码解析中,我们从 packges/vue/src/index.ts 的入口开始,了解了一个 Vue 对象的编译流程,在文中我们提到 baseCompile 函数在执行过程中会生成 AST 抽象语法树,毫无疑问这是很关键的一步,因为只有拿到生成的 AST 我们才能遍历 AST 的节点进行 transform 转换操作,比如解析 v

  • Android10 App 启动分析进程创建源码解析

    目录 正文 RootActivityContainer ActivityStartController 调用startActivityUnchecked方法 ActivityStackSupervisor 启动进程 RuntimeInit.applicationInit这个方法 正文 从前文# Android 10 启动分析之SystemServer篇 (四)中可以得知,系统在完成所有的初始化工作后,会通过 mAtmInternal.startHomeOnAllDisplays(currentU

  • Vue3源码解析watch函数实例

    目录 引言 一.watch参数类型 1. 选项options 2. 回调cb 3. 数据源source 二.watch函数 三.watch的核心:doWatch 函数 引言 想起上次面试,问了个古老的问题:watch和computed的区别.多少有点感慨,现在已经很少见这种耳熟能详的问题了,网络上八股文不少.今天,我更想分享一下从源码的层面来区别这八竿子打不着的两者.本篇针对watch做分析,下一篇分析computed. 一.watch参数类型 我们知道,vue3里的watch接收三个参数:侦听

随机推荐