创建runtime-dom包
runtime-dom 针对浏览器运行时,包括DOM API 、属性、事件处理等
runtime-dom/package.json
{
"name": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js",
"unpkg": "dist/runtime-dom.global.js",
"buildOptions": {
"name": "VueRuntimeDOM",
"formats": [
"esm-bundler",
"cjs",
"global"
]
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
pnpm install @vue/shared@workspace --filter @vue/runtime-dom
1
实现节点常用操作
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) // 搜索元素
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
比对属性方法
export const patchProp = (el, key, prevValue, nextValue) => {
if (key === 'class') {
patchClass(el, nextValue)
} else if (key === 'style') {
patchStyle(el, prevValue, nextValue);
} else if (/^on[^a-z]/.test(key)) {
patchEvent(el, key, nextValue)
} else {
patchAttr(el, key, nextValue)
}
}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
操作类名
function patchClass(el, value) { // 根据最新值设置类名
if (value == null) {
el.removeAttribute('class');
} else {
el.className = value;
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
操作样式
function patchStyle(el, prev, next) { // 更新style
const style = el.style;
for (const key in next) { // 用最新的直接覆盖
style[key] = next[key]
}
if (prev) {
for (const key in prev) {// 老的有新的没有删除
if (next[key] == null) {
style[key] = null
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
操作事件
function createInvoker(initialValue) {
const invoker = (e) => invoker.value(e);
invoker.value = initialValue;
return invoker;
}
function patchEvent(el, rawName, nextValue) { // 更新事件
const invokers = el._vei || (el._vei = {});
const exisitingInvoker = invokers[rawName]; // 是否缓存过
if (nextValue && exisitingInvoker) {
exisitingInvoker.value = nextValue;
} else {
const name = rawName.slice(2).toLowerCase(); // 转化事件是小写的
if (nextValue) {// 缓存函数
const invoker = (invokers[rawName]) = createInvoker(nextValue);
el.addEventListener(name, invoker);
} else if (exisitingInvoker) {
el.removeEventListener(name, exisitingInvoker);
invokers[rawName] = undefined
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在绑定事件的时候,绑定一个伪造的事件处理函数invoker,把真正的事件处理函数设置为invoker.value属性的值
操作属性
function patchAttr(el, key, value) { // 更新属性
if (value == null) {
el.removeAttribute(key);
} else {
el.setAttribute(key, value);
}
}
1
2
3
4
5
6
7
2
3
4
5
6
7
创建渲染器
最终我们在
index.js
中引入写好的方法,渲染选项就准备好了。 稍后将虚拟DOM转化成真实DOM会调用这些方法
import { nodeOps } from "./nodeOps"
import { patchProp } from "./patchProp"
// 准备好所有渲染时所需要的的属性
const renderOptions = Object.assign({patchProp},nodeOps);
createRenderer(renderOptions).render(
h('h1','jw'),
document.getElementById('app')
);
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
createRenderer接受渲染所需的方法,h方法为创建虚拟节点的方法。这两个方法和平台无关,所以我们将这两个方法在runtime-core中实现。