runtime-dom 实现

第一步

新建 runtime-dom 模块

packages/runtime-dom/package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"name": "@vue/runtime-dom",
"version": "1.0.0",
"module": "dist/runtime-dom.esm-bundler.js",
"unpkg": "dist/runtime-dom.global.js",
"buildOptions": {
"name": "RuntimeDOM",
"formats": [
"esm-bundler",
"esm-browser",
"cjs",
"global"
]
}
}

修改打包编译的模块名称

/package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"name": "vue3-resource",
"private": true,
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
// -----------------------------新增开始-----------------------------
"dev": "node scripts/dev.js runtime-dom -f esm"
// -----------------------------新增结束-----------------------------
},
...
}

因为可以在多平台来渲染,所以提供了 createRenderer 来让我们可以自定义渲染器,同时提供了 render 内置渲染器,来渲染 dom 元素,h 方法可以用来创建一个虚拟 dom,具体使用方法如下:

packages/runtime-dom/dist/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
<script type="module">
import { createRenderer, render, h } from '/node_modules/@vue/runtime-dom/dist/runtime-dom.esm-browser.js';

const ele = h('h1', 'xiaosheng');

// 使用默认的渲染器
render(ele, app);

// 自定义渲染器
const renderer = createRenderer({
// 创建 dom
createElement(type) {
return document.createElement('h1');
},
// 设置元素中的内容
setElementText(el, text) {
el.textContent = text;
},
// 将 dom 添加到页面中
insert(el, container) {
container.appendChild(el);
}
});
renderer.render(ele, app);
</script>
</body>
</html>

第二步

runtime-dom 是指针对浏览器的,runtime-dom 是基于 runtime-core 的,runtime-core 是跨平台的,其次,runtime-core 又依赖 reactivity 来实现响应式,因此在 runtime-dom 中需要安装以下依赖:pnpm i @vue/shared@workspace --filter @vue/runtime-dom

runtime-dom 中包含了对节点的增删改查,以及对节点属性的相关操作,具体代码如下:

packages/runtime-dom/src/nodeOps.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 对节点元素的增删改查
export const nodeOps = {
insert: (el, parent, anchor) => parent.insertBefore(el, anchor || null),
remove(el) {
const parent = el.parentNode;
parent && parent.removeChild(el);
},
createElement: (type) => document.createElement(type),
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,
};

packages/runtime-dom/src/patchProp.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import patchClass from './modules/patchClass';
import patchStyle from './modules/patchStyle';
import patchEvent from './modules/patchEvent';
import patchAttr from './modules/patchAttr';

// 对节点元素的属性操作
export default function patchProp(el, key, prevValue, nextValue) {
if (key === 'class') {
return patchClass(el, nextValue);
} else if (key === 'style') {
return patchStyle(el, prevValue, nextValue);
} else if (/^on[^a-z]/.test(key)) {
return patchEvent(el, key, nextValue)
} else {
patchAttr(el, key, nextValue)
}
}

packages/runtime-dom/src/modules/patchAttr.ts

1
2
3
4
5
6
7
export default function patchAttr(el, key, value) {
if (!value) {
el.removeAttribute(key)
} else {
el.setAttribute(key, value)
}
}

packages/runtime-dom/src/modules/patchClass.ts

1
2
3
4
5
6
7
export default function patchClass(el, value) {
if (value === null) {
el.removeAttribute('class')
} else {
el.className = value
}
}

packages/runtime-dom/src/modules/patchEvent.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function createInvoker(value) {
const invoker = (e) => invoker.value(e);
invoker.value = value;
return invoker;
}

export default function patchEvent(el, name, nextValue) {
const invokers = el._vei || (el._vei = {});
const eventName = name.slice(2).toLowerCase();

const existingInvokers = invokers[name];

if (nextValue && existingInvokers) {
return existingInvokers.value = nextValue;
}

if (nextValue) {
const invoker = invokers[name] = createInvoker(nextValue);
return el.addEventListener(eventName, invoker);
}

if (existingInvokers && !nextValue) {
el.removeEventListener(eventName, existingInvokers);
invokers[name] = undefined;
}
}

packages/runtime-dom/src/modules/patchStyle.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default function patchStyle(el, prevValue, nextValue) {
const style = el.style;
for (const key in nextValue) {
style[key] = nextValue[key];
}

if (prevValue) {
for (const key in prevValue) {
if (nextValue && !nextValue[key]) {
style[key] = null;
}
}
}
}