vue3模块创建runtime-dom源码解析
前言
runtime-dom
是针对浏览器的运行时,包括 DOM 操作、props
(例如class
、事件、样式以及其它attributes
)的更新等内容;本小节我们开启 runtime-dom
的篇章。
创建模块
在 packages/runtime-dom/
目录下创建目录文件:
│ │ └─ class="lazy" data-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/class="lazy" data-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/class="lazy" data-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
分成四种类型:class
、style
、onXxx
事件、以及其它 attributes
属性;分别用四种方法来处理这四种 prop
。
patchClass
更新 class
属性:
value
为空时,使用el.removeAttribute("class")
去掉class
属性;- 否则设置元素的
className
属性。
// runtime-dom/class="lazy" data-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/class="lazy" data-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/class="lazy" data-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/class="lazy" data-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模块创建的资料请关注编程网其它相关文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341