<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue3渲染器的核心功能:挂载与更新</title>
<style>
.bgColor {
background-color: deeppink;
}
.fontColor {
color: #fff;
font-size: 24px;
}
</style>
</head>
<body>
<div id="app"></div>
<script>
/**
* 定义文本节点和注释节点
*/
const Text = Symbol();
const Comment = Symbol();
const Fragment = Symbol();
const newVnode1 = {
type: Text,
children: "我是文本内容 ",
};
const newVnode2 = {
type: Comment,
children: "我是注释内容",
};
const fragmentNode = {
type: "ul",
children: [
{
type: Fragment,
children: [
{ type: "li", children: "item 1" },
{ type: "li", children: "item 2" },
{ type: "li", children: "item 3" },
],
},
],
};
/**
* 自定义渲染器
*/
function createRenderer(options) {
const {
createElement,
insert,
setElementText,
patchProps,
createText,
setText,
} = options;
/**
* 卸载
*/
function unmount(vnode) {
if (vnode.type === Fragment) {
vnode.children.forEach((c) => unmount(c));
return;
}
const parent = vnode.el.parentNode;
if (parent) {
parent.removeChild(vnode.el);
}
}
function render(vnode, container) {
if (vnode) {
patch(container._vnode, vnode, container);
} else {
// 删除旧节点
if (container._vnode) {
unmount(container._vnode);
}
}
container._vnode = vnode;
}
/**
* 打补丁
*/
function patch(n1, n2, container) {
if (n1 && n1.type !== n2.type) {
unmount(n1);
n1 = null;
}
const { type } = n2;
console.log(type);
if (typeof type === "string") {
if (!n1) {
mountElement(n2, container);
} else {
patchElement(n1, n2);
}
} else if (type === Text) {
console.log("文本节点");
// 文本节点
if (!n1) {
const el = (n2.el = createText(n2.children));
insert(el, container);
} else {
const el = (n2.el = n1.el);
if (n2.children !== n1.children) {
setText(el, n2.children);
}
}
} else if (type === Fragment) {
// 片断
if (!n1) {
// 将Fragment的 children逐个挂载
n2.children.forEach((c) => patch(null, c, container));
} else {
patchChildren(n1, n2, container);
}
}
}
/**
* 更新子节点
*/
function patchElement(n1, n2) {
const el = (n2.el = n1.el);
const oldProps = n1.props;
const newProps = n2.props;
for (const key in newProps) {
if (newProps[key] !== oldProps[key]) {
patchProps(el, key, oldProps[key], newProps[key]);
}
}
for (const key in oldProps) {
if (!(key in newProps)) {
patchProps(el, key, oldProps[key], null);
}
}
patchChildren(n1, n2, el);
}
/**
* 更新children
*/
function patchChildren(n1, n2, container) {
if (typeof n2.children === "string") {
if (Array.isArray(n1.children)) {
n1.children.forEach((c) => unmount(c));
}
setElementText(container, n2.children);
} else if (Array.isArray(n2.children)) {
if (Array.isArray(n1.children)) {
// 此处是核心的 Diff算法
n1.children.forEach((c) => unmount(c));
n2.children.forEach((c) => patch(null, c, container));
} else {
setElementText(container, "");
// 挂载新节点
n2.children.forEach((c) => patch(null, c, container));
}
} else {
// 新子节点不存在,则卸载旧节点
if (Array.isArray(n1.children)) {
n1.children.forEach((c) => unmount(c));
} else if (typeof n1.children === "string") {
setElementText(container, "");
}
}
}
// 主要改动这里
function mountElement(vnode, container) {
const el = (vnode.el = createElement(vnode.type));
if (typeof vnode.children === "string") {
setElementText(el, vnode.children);
} else if (Array.isArray(vnode.children)) {
vnode.children.forEach((child) => {
patch(null, child, el);
});
}
if (vnode.props) {
for (const key in vnode.props) {
const value = vnode.props[key];
patchProps(el, key, null, value);
}
}
insert(el, container);
}
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "INPUT") {
return false;
}
return key in el;
}
return { render };
}
/**
* 公共函数
*/
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "INPUT") {
console.log("el", el);
return false;
}
return key in el;
}
// 测试代码
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
parent.insertBefore(el, anchor);
},
createText(text) {
return document.createTextNode(text);
},
setText(el, text) {
el.nodeValue = text;
},
patchProps(el, key, prevValue, nextValue) {
if (/^on/.test(key)) {
// const name = key.slice(2).toLowerCase();
// prevValue && el.removeEventListener(name, prevValue)
// el.addEventListener(name, nextValue);
const invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
const name = key.slice(2).toLowerCase();
if (nextValue) {
if (!invoker) {
// 绑定一个伪造的事件处理函数invoker
invoker = el._vei[key] = (e) => {
// e.timeStamp 触发事件的时间
if (e.timeStamp < invoker.attached) {
return;
}
if (Array.isArray(invoker.value)) {
invoker.value.forEach((fn) => fn(e));
} else {
invoker.value(e);
}
};
invoker.value = nextValue;
// 绑定事件的时间
invoker.attached = performance.now();
el.addEventListener(name, invoker);
} else {
invoker.value = nextValue;
}
} else {
el.removeEventListener(name, invoker);
}
} else if (key === "class") {
el.className = nextValue || "";
} else if (shouldSetAsProps(el, key, nextValue)) {
const type = typeof el[key];
if (type === "boolean" && nextValue === "") {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
el.setAttribute(key, nextValue);
}
},
});
// 虚拟节点对象
const vnode = {
type: "div",
props: {
id: "foo",
},
children: [
{
type: "div",
children: [
{
type: "p",
children: "千山鸟飞绝,万径人踪灭。",
},
{
type: "p",
children: "孤舟蓑笠翁,独钓寒江雪。",
},
newVnode1,
newVnode1,
newVnode1,
],
props: {
class: "bgColor fontColor",
onClick: [
() => {
alert("clicked 1");
},
() => {
alert("clicked 2");
},
],
onContextmenu: () => {
alert("contextmenu");
},
},
},
{
type: "button",
props: {
disabled: "", // 这是一个特殊属性案例处理
},
children: "这是一个按钮",
},
{
type: "input",
props: {
form: "form1",
},
},
fragmentNode,
],
};
renderer.render(vnode, document.querySelector("#app"));
</script>
</body>
</html>