节点的渲染
土豆 2023/7/14 vue
# 渲染器设计
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
function createRenderer(options) {
const { createElement, insert, setElementText } = options;
function mountElement(vnode, container) {
// 调用 createElement 函数创建元素
const el = createElement(vnode.type);
if (typeof vnode.children === "string") {
// 调用 setElementText 设置元素的文本节点
setElementText(el, vnode.children);
}
// 调用 insert 函数将元素插入到容器内
insert(el, container);
}
// 给节点打补丁
function patch(n1, n2, container) {
if (!n1) {
mountElement(n2, container);
}
}
// 渲染节点
function render(vnode, container) {
if (vnode) {
patch(container._vnode, vnode, container);
} else {
if (container._vnode) {
container.innerHTML = "";
}
}
container._vnode = vnode;
}
return {
render,
};
}
const vnode = {
type: "h1",
children: "hello",
};
const renderer2 = createRenderer({
// 用于创建元素
createElement(tag) {
console.log(`创建元素 ${tag}`);
return { tag };
},
// 用于设置元素的文本节点
setElementText(el, text) {
console.log(`设置 ${JSON.stringify(el)} 的文本内容:${text}`);
el.text = text;
},
// 用于在给定的 parent 下添加指定元素
insert(el, parent, anchor = null) {
console.log(
`将 ${JSON.stringify(el)} 添加到 ${JSON.stringify(parent)} 下`
);
parent.children = el;
},
});
const container = { type: "root" };
renderer2.render(vnode, container);
</script>
# 挂载子节点和元素属性
子节点:children 数组
元素属性:id
const vnode = {
type: "div",
props: {
id: "foo",
},
children: [
{
type: "p",
children: "hello",
},
],
};
挂载节点
function mountElement(vnode, container) {
const el = createElement(vnode.type);
if (typeof vnode.children === "string") {
setElementText(el, vnode.children);
} else if (Array.isArray(vnode.children)) {
// 如果 children 是数组,则遍历每一个子节点,并调用 patch 函数挂载它
vnode.children.forEach((child) => {
patch(null, child, el);
});
}
// 如果 vnode.props 存在才处理它
if (vnode.props) {
// 遍历 vnode.props
for (const key in vnode.props) {
// 调用 setAttribute 将属性设置到元素上
el.setAttribute(key, vnode.props[key]);
}
}
insert(el, container);
}
function patch(n1, n2, container) {
// 旧的节点没有
if (!n1) {
// 挂载节点
mountElement(n2, container);
}
}
完整代码
See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
function createRenderer(options) {
const { createElement, insert, setElementText } = options;
function mountElement(vnode, container) {
const el = createElement(vnode.type);
if (typeof vnode.children === "string") {
setElementText(el, vnode.children);
} else if (Array.isArray(vnode.children)) {
// 如果 children 是数组,则遍历每一个子节点,并调用 patch 函数挂载它
vnode.children.forEach((child) => {
patch(null, child, el);
});
}
// 如果 vnode.props 存在才处理它
if (vnode.props) {
// 遍历 vnode.props
for (const key in vnode.props) {
// 调用 setAttribute 将属性设置到元素上
el.setAttribute(key, vnode.props[key]);
}
}
insert(el, container);
}
function patch(n1, n2, container) {
// 旧的节点没有
if (!n1) {
// 挂载节点
mountElement(n2, container);
}
}
function render(vnode, container) {
if (vnode) {
patch(container._vnode, vnode, container);
} else {
if (container._vnode) {
container.innerHTML = "";
}
}
container._vnode = vnode;
}
return {
render,
};
}
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
console.log(el, parent, anchor);
// 把el插入到parent里
// anchor 插入的位置
parent.insertBefore(el, anchor);
},
});
const vnode = {
type: "div",
props: {
id: "foo",
},
children: [
{
type: "p",
children: "hello",
},
],
};
renderer.render(vnode, document.querySelector("#app"));
</script>
# 正确地设置元素属性
el.disabled = true
设置 DOM Properties
const vnode = {
type: "button",
props: {
disabled: "",
},
children: "Button",
};
设置属性
// 判断是否作为属性设置
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "input") {
return false;
}
return key in el;
}
for (const key in vnode.props) {
// 判断是否是form或input
const value = vnode.props[key];
if (shouldSetAsProps(el, key, value)) {
console.log(el, key);
const type = typeof el[key];
if (type === "boolean" && value === "") {
el[key] = true;
} else {
el[key] = value;
}
} else {
el.setAttribute(key, vnode.props[key]);
}
}
简化: 提取 patchProps
if(vnode.props){
for (const key in vnode.props){
patchProps(el,key,null,vnode.props[key])
}
}
const renderer = createRenderer({
...,
patchProps(el,key,preValue,nextValue){
if(shouldSetAsProps(el,key,nextValue)){
console.log(el,key)
const type = typeof el[key];
if(type === 'boolean' && nextValue === ''){
el[key] = true;
}else {
el[key] = nextValue;
}
}else {
el.setAttribute(key,vnode.props[key])
}
}
})
完整代码
See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "input") {
return false;
}
return key in el;
}
function createRenderer(options) {
const { createElement, insert, setElementText, patchProps } = options;
function mountElement(vnode, container) {
const 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) {
patchProps(el, key, null, vnode.props[key]);
}
}
insert(el, container);
}
function patch(n1, n2, container) {
if (!n1) {
mountElement(n2, container);
}
}
function render(vnode, container) {
if (vnode) {
patch(container._vnode, vnode, container);
} else {
if (container._vnode) {
container.innerHTML = "";
}
}
container._vnode = vnode;
}
return {
render,
};
}
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
console.log(el, parent, anchor);
parent.insertBefore(el, anchor);
},
patchProps(el, key, preValue, nextValue) {
if (shouldSetAsProps(el, key, nextValue)) {
console.log(el, key);
const type = typeof el[key];
if (type === "boolean" && nextValue === "") {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
el.setAttribute(key, vnode.props[key]);
}
},
});
const vnode = {
type: "button",
props: {
disabled: "",
},
children: "Button",
};
renderer.render(vnode, document.querySelector("#app"));
</script>
# class 的处理
const vnode = {
type: "p",
props: {
class: "foo bar baz",
},
children: "text",
};
设置 class
const renderer = createRenderer({
// 省略
patchProps(el, key, preValue, nextValue) {
if (key === "class") {
el.className = nextValue || "";
} else if (shouldSetAsProps(el, key, nextValue)) {
// 省略
} else {
// 省略
}
},
});
完整代码
See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "input") {
return false;
}
return key in el;
}
function createRenderer(options) {
const { createElement, insert, setElementText, patchProps } = options;
function mountElement(vnode, container) {
const 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) {
patchProps(el, key, null, vnode.props[key]);
}
}
insert(el, container);
}
function patch(n1, n2, container) {
if (!n1) {
mountElement(n2, container);
}
}
function render(vnode, container) {
if (vnode) {
patch(container._vnode, vnode, container);
} else {
if (container._vnode) {
container.innerHTML = "";
}
}
container._vnode = vnode;
}
return {
render,
};
}
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
console.log(el, parent, anchor);
parent.insertBefore(el, anchor);
},
patchProps(el, key, preValue, nextValue) {
if (key === "class") {
el.className = nextValue || "";
} else if (shouldSetAsProps(el, key, nextValue)) {
console.log(el, key);
const type = typeof el[key];
if (type === "boolean" && nextValue === "") {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
el.setAttribute(key, vnode.props[key]);
}
},
});
const vnode = {
type: "p",
props: {
class: "foo bar baz",
},
children: "text",
};
renderer.render(vnode, document.querySelector("#app"));
</script>
# 卸载操作
// 初次挂载
renderer.render(vnode, document.querySelector("#app"));
// 新 vnode 为 null,意味着卸载之前渲染的内容
renderer.render(null, document.querySelector("#app"));
卸载节点
// 给元素修改和添加属性
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);
}
}
}
// 打补丁
function patch(n1, n2, container) {
// n1 之前的节点 n2新节点
// 如果之前没有挂载节点则挂载
if (!n1) {
mountElement(n2, container);
} else {
// 否则更新属性
patchElement(n1, n2);
}
}
// 卸载节点
function unmount(vnode) {
const parent = vnode.el.parentNode;
if (parent) {
parent.removeChild(vnode.el);
}
}
function render(vnode, container) {
if (vnode) {
// 新节点有,则打补丁
patch(container._vnode, vnode, container);
} else {
// vnode=null 新节点每有,之前挂载过节点则卸载节点
if (container._vnode) {
unmount(container._vnode);
}
}
container._vnode = vnode;
}
完整代码
See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "input") {
return false;
}
return key in el;
}
function createRenderer(options) {
const { createElement, insert, setElementText, patchProps } = options;
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) {
patchProps(el, key, null, vnode.props[key]);
}
}
insert(el, 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);
}
}
}
// 打补丁
function patch(n1, n2, container) {
// n1 之前的节点 n2新节点
// 如果之前没有挂载节点则挂载
if (!n1) {
mountElement(n2, container);
} else {
// 否则更新属性
patchElement(n1, n2);
}
}
// 卸载节点
function unmount(vnode) {
const parent = vnode.el.parentNode;
if (parent) {
parent.removeChild(vnode.el);
}
}
function render(vnode, container) {
if (vnode) {
// 新节点有,则打补丁
patch(container._vnode, vnode, container);
} else {
// vnode=null 新节点每有,之前挂载过节点则卸载节点
if (container._vnode) {
unmount(container._vnode);
}
}
container._vnode = vnode;
}
return {
render,
};
}
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
console.log(el, parent, anchor);
parent.insertBefore(el, anchor);
},
patchProps(el, key, preValue, nextValue) {
if (key === "class") {
el.className = nextValue || "";
} else if (shouldSetAsProps(el, key, nextValue)) {
console.log(el, key);
const type = typeof el[key];
if (type === "boolean" && nextValue === "") {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
el.setAttribute(key, vnode.props[key]);
}
},
});
const vnode = {
type: "p",
props: {
class: "foo bar baz",
},
children: "text",
};
renderer.render(vnode, document.querySelector("#app"));
renderer.render(null, document.querySelector("#app"));
</script>
# 区分 vnode 的类型
节点的类型判断
const oldVNode = {
type: "p",
children: "text",
};
const newVNode = {
type: "div",
children: "hello",
};
节点区分
function patch(n1, n2, container) {
// 如果 n1 存在,则对比 n1 和 n2 的类型
if (n1 && n1.type !== n2.type) {
// 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
// 卸载旧的node
unmount(n1);
n1 = null;
}
// 代码运行到这里,证明 n1 和 n2 所描述的内容相同
const { type } = n2;
// 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
if (typeof type === "string") {
if (!n1) {
// n1不存在,及旧的节点不存在,则直接挂载新的
mountElement(n2, container);
} else {
// 对比n1,n2打上补丁
patchElement(n1, n2);
}
} else if (typeof type === "object") {
// 如果 n2.type 的值的类型是对象,则它描述的是组件
}
}
完整代码
See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "input") {
return false;
}
return key in el;
}
function createRenderer(options) {
const { createElement, insert, setElementText, patchProps } = options;
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) {
patchProps(el, key, null, vnode.props[key]);
}
}
insert(el, 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);
}
}
}
function patch(n1, n2, container) {
// 如果 n1 存在,则对比 n1 和 n2 的类型
if (n1 && n1.type !== n2.type) {
// 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
// 卸载旧的node
unmount(n1);
n1 = null;
}
// 代码运行到这里,证明 n1 和 n2 所描述的内容相同
const { type } = n2;
// 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
if (typeof type === "string") {
if (!n1) {
// n1不存在,及旧的节点不存在,则直接挂载新的
mountElement(n2, container);
} else {
// 对比n1,n2打上补丁
patchElement(n1, n2);
}
} else if (typeof type === "object") {
// 如果 n2.type 的值的类型是对象,则它描述的是组件
}
}
function unmount(vnode) {
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) {
// container.innerHTML = "";
unmount(container._vnode);
}
}
container._vnode = vnode;
}
return {
render,
};
}
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
console.log(el, parent, anchor);
parent.insertBefore(el, anchor);
},
patchProps(el, key, preValue, nextValue) {
if (key === "class") {
el.className = nextValue || "";
} else if (shouldSetAsProps(el, key, nextValue)) {
console.log(el, key);
const type = typeof el[key];
if (type === "boolean" && nextValue === "") {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
el.setAttribute(key, vnode.props[key]);
}
},
});
const vnode = {
type: "p",
props: {
class: "foo bar baz",
},
children: "text",
};
renderer.render(vnode, document.querySelector("#app"));
const newVnode = {
type: "div",
props: {
id: "foo",
},
children: "hello",
};
setTimeout(() => {
renderer.render(newVnode, document.querySelector("#app"));
}, 1000);
</script>
# 事件处理
// 情况1
const vnode = {
type: "p",
props: {
// 使用 onXxx 描述事件
onClick: () => {
alert("clicked");
},
},
children: "text",
};
// 情况2
const vnode = {
type: "p",
props: {
onClick: () => {
alert("clicked");
},
onContextmenu: () => {
alert("contextmenu");
},
},
children: "text",
};
// 情况3
const vnode = {
type: "p",
props: {
onClick: [
() => {
alert("clicked 1");
},
() => {
alert("clicked 2");
},
],
},
children: "text",
};
事件处理
const renderer = createRenderer({
// 省略...
patchProps(el, key, preValue, nextValue) {
// 匹配以 on 开头的属性,视其为事件
if (/^on/.test(key)) {
// 获取为该元素伪造的事件处理函数 invoker
let invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
// 根据属性名称得到对应的事件名称,例如 onClick ---> click
const name = key.slice(2).toLowerCase();
if (nextValue) {
if (!invoker) {
// 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
// vei 是 vue event invoker 的首字母缩写
invoker = el._vei[key] = (e) => {
// 当伪造的事件处理函数执行时,会执行真正的事件处理函数
if (Array.isArray(invoker.value)) {
invoker.value.forEach((fn) => fn(e));
} else {
invoker.value(e);
}
};
// 将真正的事件处理函数赋值给 invoker.value
invoker.value = nextValue;
// 绑定 invoker 作为事件处理函数
el.addEventListener(name, invoker);
} else {
// 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
invoker.value = nextValue;
}
} else if (invoker) {
// 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
el.removeEventListener(name, invoker);
}
} else if (key === "class") {
// 省略...
} else if (shouldSetAsProps(el, key, nextValue)) {
// 省略...
} else {
// 省略...
}
},
});
# 事件冒泡与更新时机问题
const { effect, ref } = VueReactivity;
const bol = ref(false);
effect(() => {
// 创建 vnode
const vnode = {
type: "div",
props: bol.value
? {
onClick: () => {
alert("父元素 clicked");
},
}
: {},
children: [
{
type: "p",
props: {
onClick: () => {
bol.value = true;
},
},
children: "text",
},
],
};
// 渲染 vnode
renderer.render(vnode, document.querySelector("#app"));
});
更新时机
const renderer = createRenderer({
// 省略...
patchProps(el, key, preValue, nextValue) {
// 匹配以 on 开头的属性,视其为事件
if (/^on/.test(key)) {
// 获取为该元素伪造的事件处理函数 invoker
let invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
// 根据属性名称得到对应的事件名称,例如 onClick ---> click
const name = key.slice(2).toLowerCase();
if (nextValue) {
if (!invoker) {
// 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
// vei 是 vue event invoker 的首字母缩写
invoker = el._vei[key] = (e) => {
// 如果事件发生的时间早于事件处理函数绑定的时间,则不执行事件处理函数
if (e.timeStamp < invoker.attached) {
return;
}
// 当伪造的事件处理函数执行时,会执行真正的事件处理函数
if (Array.isArray(invoker.value)) {
invoker.value.forEach((fn) => fn(e));
} else {
invoker.value(e);
}
};
// 将真正的事件处理函数赋值给 invoker.value
invoker.value = nextValue;
invoker.attached = performance.now();
// 绑定 invoker 作为事件处理函数
el.addEventListener(name, invoker);
} else {
// 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
invoker.value = nextValue;
}
} else if (invoker) {
// 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
el.removeEventListener(name, invoker);
}
} else if (key === "class") {
// 省略...
} else if (shouldSetAsProps(el, key, nextValue)) {
// 省略...
} else {
// 省略...
}
},
});
完整代码
See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "input") {
return false;
}
return key in el;
}
function createRenderer(options) {
const { createElement, insert, setElementText, patchProps } = options;
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) {
patchProps(el, key, null, vnode.props[key]);
}
}
insert(el, 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);
}
}
}
function patch(n1, n2, container) {
// 如果 n1 存在,则对比 n1 和 n2 的类型
if (n1 && n1.type !== n2.type) {
// 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
// 卸载旧的node
unmount(n1);
n1 = null;
}
// 代码运行到这里,证明 n1 和 n2 所描述的内容相同
const { type } = n2;
// 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
if (typeof type === "string") {
if (!n1) {
// n1不存在,及旧的节点不存在,则直接挂载新的
mountElement(n2, container);
} else {
// 对比n1,n2打上补丁
patchElement(n1, n2);
}
} else if (typeof type === "object") {
// 如果 n2.type 的值的类型是对象,则它描述的是组件
}
}
function unmount(vnode) {
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) {
// container.innerHTML = "";
unmount(container._vnode);
}
}
container._vnode = vnode;
}
return {
render,
};
}
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
console.log(el, parent, anchor);
parent.insertBefore(el, anchor);
},
patchProps(el, key, preValue, nextValue) {
// 匹配以 on 开头的属性,视其为事件
if (/^on/.test(key)) {
// 获取为该元素伪造的事件处理函数 invoker
let invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
// 根据属性名称得到对应的事件名称,例如 onClick ---> click
const name = key.slice(2).toLowerCase();
if (nextValue) {
if (!invoker) {
// 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
// vei 是 vue event invoker 的首字母缩写
invoker = el._vei[key] = (e) => {
// 如果事件发生的时间早于事件处理函数绑定的时间,则不执行事件处理函数
if (e.timeStamp < invoker.attached) {
return;
}
// 当伪造的事件处理函数执行时,会执行真正的事件处理函数
if (Array.isArray(invoker.value)) {
invoker.value.forEach((fn) => fn(e));
} else {
invoker.value(e);
}
};
// 将真正的事件处理函数赋值给 invoker.value
invoker.value = nextValue;
invoker.attached = performance.now();
// 绑定 invoker 作为事件处理函数
el.addEventListener(name, invoker);
} else {
// 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
invoker.value = nextValue;
}
} else if (invoker) {
// 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
el.removeEventListener(name, invoker);
}
} else if (key === "class") {
el.className = nextValue || "";
} else if (shouldSetAsProps(el, key, nextValue)) {
console.log(el, key);
const type = typeof el[key];
if (type === "boolean" && nextValue === "") {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
el.setAttribute(key, vnode.props[key]);
}
},
});
const { effect, ref } = VueReactivity;
const bol = ref(false);
effect(() => {
// 创建 vnode
const vnode = {
type: "div",
props: bol.value
? {
onClick: () => {
alert("父元素 clicked");
},
}
: {},
children: [
{
type: "p",
props: {
onClick: () => {
bol.value = true;
},
},
children: "text",
},
],
};
// 渲染 vnode
renderer.render(vnode, document.querySelector("#app"));
});
</script>
# 更新子节点
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, "");
}
// 如果也没有旧子节点,那么什么都不需要做
}
}
完整代码
See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "input") {
return false;
}
return key in el;
}
function createRenderer(options) {
const { createElement, insert, setElementText, patchProps } = options;
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) {
patchProps(el, key, null, vnode.props[key]);
}
}
insert(el, container);
}
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 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);
}
function patch(n1, n2, container) {
// 如果 n1 存在,则对比 n1 和 n2 的类型
if (n1 && n1.type !== n2.type) {
// 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
// 卸载旧的node
unmount(n1);
n1 = null;
}
// 代码运行到这里,证明 n1 和 n2 所描述的内容相同
const { type } = n2;
// 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
if (typeof type === "string") {
if (!n1) {
// n1不存在,及旧的节点不存在,则直接挂载新的
mountElement(n2, container);
} else {
// 对比n1,n2打上补丁
patchElement(n1, n2);
}
} else if (typeof type === "object") {
// 如果 n2.type 的值的类型是对象,则它描述的是组件
}
}
function unmount(vnode) {
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) {
// container.innerHTML = "";
unmount(container._vnode);
}
}
container._vnode = vnode;
}
return {
render,
};
}
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
console.log(el, parent, anchor);
parent.insertBefore(el, anchor);
},
patchProps(el, key, preValue, nextValue) {
// 匹配以 on 开头的属性,视其为事件
if (/^on/.test(key)) {
// 获取为该元素伪造的事件处理函数 invoker
let invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
// 根据属性名称得到对应的事件名称,例如 onClick ---> click
const name = key.slice(2).toLowerCase();
if (nextValue) {
if (!invoker) {
// 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
// vei 是 vue event invoker 的首字母缩写
invoker = el._vei[key] = (e) => {
// 如果事件发生的时间早于事件处理函数绑定的时间,则不执行事件处理函数
if (e.timeStamp < invoker.attached) {
return;
}
// 当伪造的事件处理函数执行时,会执行真正的事件处理函数
if (Array.isArray(invoker.value)) {
invoker.value.forEach((fn) => fn(e));
} else {
invoker.value(e);
}
};
// 将真正的事件处理函数赋值给 invoker.value
invoker.value = nextValue;
invoker.attached = performance.now();
// 绑定 invoker 作为事件处理函数
el.addEventListener(name, invoker);
} else {
// 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
invoker.value = nextValue;
}
} else if (invoker) {
// 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
el.removeEventListener(name, invoker);
}
} else if (key === "class") {
el.className = nextValue || "";
} else if (shouldSetAsProps(el, key, nextValue)) {
console.log(el, key);
const type = typeof el[key];
if (type === "boolean" && nextValue === "") {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
el.setAttribute(key, vnode.props[key]);
}
},
});
const oldVnode = {
type: "div",
children: "test",
};
renderer.render(oldVnode, document.querySelector("#app"));
const newVnode = {
type: "div",
children: null,
};
renderer.render(newVnode, document.querySelector("#app"));
</script>
# 文本节点
const Text = Symbol();
const vnode = {
type: Text,
children: "Some Text",
};
文本节点处理
function patch(n1, n2, container) {
// 省略...
if (typeof type === "string") {
// 省略...
} else if (type === Text) {
if (!n1) {
// n1不存在,及旧的节点不存在,则直接挂载新的
const el = (n2.el = createText(n2.children));
// 将文本节点插入到容器中
insert(el, container);
} else {
// 如果旧 vnode 存在,只需要使用新文本节点的文本内容更新旧文本节点即可
const e1 = (n2.el = n1.el);
if (n2.children !== n1.children) {
e1.nodeValue = n2.children;
}
}
}
}
完整代码
See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
function shouldSetAsProps(el, key, value) {
if (key === "form" && el.tagName === "input") {
return false;
}
return key in el;
}
function createRenderer(options) {
const {
createElement,
insert,
setElementText,
patchProps,
createText,
setText,
} = options;
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) {
patchProps(el, key, null, vnode.props[key]);
}
}
insert(el, container);
}
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 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);
}
function patch(n1, n2, container) {
// 如果 n1 存在,则对比 n1 和 n2 的类型
if (n1 && n1.type !== n2.type) {
// 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
// 卸载旧的node
unmount(n1);
n1 = null;
}
// 代码运行到这里,证明 n1 和 n2 所描述的内容相同
const { type } = n2;
// 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
if (typeof type === "string") {
if (!n1) {
// n1不存在,及旧的节点不存在,则直接挂载新的
mountElement(n2, container);
} else {
// 对比n1,n2打上补丁
patchElement(n1, n2);
}
} else if (type === Text) {
if (!n1) {
// n1不存在,及旧的节点不存在,则直接挂载新的
const el = (n2.el = createText(n2.children));
// 将文本节点插入到容器中
insert(el, container);
} else {
// 如果旧 vnode 存在,只需要使用新文本节点的文本内容更新旧文本节点即可
const e1 = (n2.el = n1.el);
if (n2.children !== n1.children) {
e1.nodeValue = n2.children;
}
}
}
}
function unmount(vnode) {
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) {
// container.innerHTML = "";
unmount(container._vnode);
}
}
container._vnode = vnode;
}
return {
render,
};
}
const renderer = createRenderer({
createElement(tag) {
return document.createElement(tag);
},
setElementText(el, text) {
el.textContent = text;
},
insert(el, parent, anchor = null) {
console.log(el, parent, anchor);
parent.insertBefore(el, anchor);
},
createText(text) {
return document.createTextNode(text);
},
setText(el, text) {
el.nodeValue = text;
},
patchProps(el, key, preValue, nextValue) {
// 匹配以 on 开头的属性,视其为事件
if (/^on/.test(key)) {
// 获取为该元素伪造的事件处理函数 invoker
let invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
// 根据属性名称得到对应的事件名称,例如 onClick ---> click
const name = key.slice(2).toLowerCase();
if (nextValue) {
if (!invoker) {
// 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
// vei 是 vue event invoker 的首字母缩写
invoker = el._vei[key] = (e) => {
// 如果事件发生的时间早于事件处理函数绑定的时间,则不执行事件处理函数
if (e.timeStamp < invoker.attached) {
return;
}
// 当伪造的事件处理函数执行时,会执行真正的事件处理函数
if (Array.isArray(invoker.value)) {
invoker.value.forEach((fn) => fn(e));
} else {
invoker.value(e);
}
};
// 将真正的事件处理函数赋值给 invoker.value
invoker.value = nextValue;
invoker.attached = performance.now();
// 绑定 invoker 作为事件处理函数
el.addEventListener(name, invoker);
} else {
// 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
invoker.value = nextValue;
}
} else if (invoker) {
// 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
el.removeEventListener(name, invoker);
}
} else if (key === "class") {
el.className = nextValue || "";
} else if (shouldSetAsProps(el, key, nextValue)) {
console.log(el, key);
const type = typeof el[key];
if (type === "boolean" && nextValue === "") {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
el.setAttribute(key, vnode.props[key]);
}
},
});
const Text = Symbol();
const newVnode = {
type: Text,
children: "Some Text",
};
renderer.render(newVnode, document.querySelector("#app"));
const oldVnode = {
type: Text,
children: "Some Text2",
};
renderer.render(oldVnode, document.querySelector("#app"));
</script>
# Fragment
const Fragment = Symbol();
const vnode = {
type: "div",
children: [
{
type: Fragment,
children: [
{ type: "p", children: "text 1" },
{ type: "p", children: "text 2" },
{ type: "p", children: "text 3" },
],
},
],
};
Fragment 处理
function patch(n1, n2, container) {
// 省略...
if (typeof type === "string") {
// 省略...
} else if (type === Text) {
// 省略...
} else if (type === Fragment) {
if (!n1) {
// 如果旧 vnode 不存在,则只需要将 Fragment 的 children 逐个挂载即可
n2.children.forEach((c) => patch(null, c, container));
} else {
// 如果旧 vnode 存在,则只需要更新 Fragment 的 children 即可
patchChildren(n1, n2, container);
}
}
}
See More
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<div id="app"></div>
<script src="https://unpkg.com/@vue/reactivity@3.0.5/dist/reactivity.global.js"></script>
<script>
function shouldSetAsProps(el,key,value){
if(key === 'form' && el.tagName === 'input'){
return false;
}
return key in el;
}
function createRenderer(options){
const {
createElement,
insert,
setElementText,
patchProps,
createText,
setText
} = options;
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){
patchProps(el,key,null,vnode.props[key])
}
}
insert(el,container)
}
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 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);
}
function patch(n1,n2,container){
// 如果 n1 存在,则对比 n1 和 n2 的类型
if(n1 && n1.type !== n2.type){
// 如果新旧 vnode 的类型不同,则直接将旧 vnode 卸载
// 卸载旧的node
unmount(n1)
n1 = null;
}
// 代码运行到这里,证明 n1 和 n2 所描述的内容相同
const {type} = n2;
// 如果n2.type 的值是字符串类型,则它描述的是普通标签元素
if(typeof type === 'string'){
if(!n1){
// n1不存在,及旧的节点不存在,则直接挂载新的
mountElement(n2,container)
}else {
// 对比n1,n2打上补丁
patchElement(n1, n2)
}
} else if (type === Text){
if(!n1){
// n1不存在,及旧的节点不存在,则直接挂载新的
const el = n2.el = createText(n2.children);
// 将文本节点插入到容器中
insert(el,container)
}else {
// 如果旧 vnode 存在,只需要使用新文本节点的文本内容更新旧文本节点即可
const e1 = n2.el = n1.el;
if(n2.children!== n1.children){
e1.nodeValue = n2.children
}
}
}else if(type === Fragment){
if(!n1){
// 如果旧 vnode 不存在,则只需要将 Fragment 的 children 逐个挂载即可
n2.children.forEach(c => patch(null,c,container))
}else {
// 如果旧 vnode 存在,则只需要更新 Fragment 的 children 即可
patchChildren(n1,n2,container)
}
}
}
function unmount(vnode){
// 在卸载时,如果卸载的 vnode 类型为 Fragment,则需要卸载其 children
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){
// container.innerHTML = "";
unmount(container._vnode)
}
}
container._vnode = vnode;
}
return {
render
}
}
const renderer = createRenderer({
createElement(tag){
return document.createElement(tag)
},
setElementText(el,text){
el.textContent = text;
},
insert(el,parent,anchor = null){
console.log(el,parent,anchor)
parent.insertBefore(el,anchor);
},
createText(text){
return document.createTextNode(text);
},
setText(el,text){
el.nodeValue = text;
},
patchProps(el,key,preValue,nextValue){
// 匹配以 on 开头的属性,视其为事件
if(/^on/.test(key)){
// 获取为该元素伪造的事件处理函数 invoker
let invokers = el._vei || (el._vei = {});
let invoker = invokers[key];
// 根据属性名称得到对应的事件名称,例如 onClick ---> click
const name = key.slice(2).toLowerCase();
if(nextValue){
if(!invoker){
// 如果没有 invoker,则将一个伪造的 invoker 缓存到 el._vei 中
// vei 是 vue event invoker 的首字母缩写
invoker = el._vei[key] = (e)=>{
// 如果事件发生的时间早于事件处理函数绑定的时间,则不执行事件处理函数
if (e.timeStamp < invoker.attached) {
return
}
// 当伪造的事件处理函数执行时,会执行真正的事件处理函数
if(Array.isArray(invoker.value)){
invoker.value.forEach(fn=>fn(e))
}else {
invoker.value(e)
}
}
// 将真正的事件处理函数赋值给 invoker.value
invoker.value = nextValue;
invoker.attached = performance.now()
// 绑定 invoker 作为事件处理函数
el.addEventListener(name,invoker)
}else {
// 如果 invoker 存在,意味着更新,并且只需要更新 invoker.value的值即可
invoker.value = nextValue;
}
}else if(invoker){
// 新的事件绑定函数不存在,且之前绑定的 invoker 存在,则移除绑定
el.removeEventListener(name,invoker);
}
}else if(key === 'class'){
el.className = nextValue || '';
}else if(shouldSetAsProps(el,key,nextValue)){
console.log(el,key)
const type = typeof el[key];
if(type === 'boolean' && nextValue === ''){
el[key] = true;
}else {
el[key] = nextValue;
}
}else {
el.setAttribute(key,vnode.props[key])
}
}
})
const Fragment = Symbol()
const vnode = {
type: 'div',
children: [
{
type: Fragment,
children: [
{ type: 'p', children: 'text 1' },
{ type: 'p', children: 'text 2' },
{ type: 'p', children: 'text 3' }
]
}
]
}
renderer.render(vnode, document.querySelector('#app'))
</script>