虚拟 DOM 其实就是用来描述真实 DOM 的普通 JavaScript 对象,渲染器会把这个对象渲染为真实 DOM 元素。
那么组件又是什么呢?组件和虚拟 DOM 有什么关系?渲染器如何渲染组件?接下来,我们就来讨论这些问题。
其实虚拟 DOM 除了能够 描述真实 DOM 之外,还能够描述组件。例如使用 组件并不是真实的 DOM 元素,那么如何使用虚拟 DOM 来描述呢?想要弄明白这个问题,就需要先搞清楚组件的本质是什么。一句话总结:组件就是一组 DOM 元素的封装,这组 DOM 元素就是组件要渲染的内容,因此我们可以定义一个函数来代表组件,而 函数的返回值 就代表组件要渲染的内容: 可以看到,组件的返回值也是虚拟 DOM,它代表组件要渲染的内容。 就像 如果 再来看 记得组件的本质: 可以看到,非常简单。首先调用 大家可以在这里举一反三,比如:组件一定得是函数么?当然不是,我们完全可以使用一个 JavaScript 对象来表达组件,例如: 这里我们使用一个对象来代表组件,该对象有一个函数,叫作 为了完成组件的渲染,我们需要修改 首先,修改渲染器的判断条件: 现在我们使用对象而不是函数来表达组件,因此要将 接着,修改 上述代码中, 可以发现,我们只做了很小的修改,就能够满足用对象来表达组件的需求。那么大家可以继续发挥想象力,看看能否创造出其他的组件表达方式。其实 Vue.js 中的有状态组件就是使用对象结构来表达的。{ tag: 'div' } 来描述 const MyComponent = function () {
//函数的返回值 就代表组件要渲染的内容
return {
tag: "div",
props: {
onClick: () => alert("hello"),
},
children: "click me",
};
};
搞清楚了组件的本质,我们就可以定义用虚拟 DOM来描述组件了。很简单,我们可以让虚拟 DOM 对象中的 tag 属性来存储组件函数: const vnode = {
tag: MyComponent
}
tag: 'div' 用来描述 tag: MyComponent 用来描述组件,只不过此时的 tag 属性不是标签名称,而是组件函数。为了能够渲染组件,需要渲染器的支持。修改前面提到的 renderer 函数,如下所示:
function renderer(vnode, container) {
if (typeof vnode.tag === "string") {
// 说明 vnode 描述的是标签元素
mountElement(vnode, container);
} else if (typeof vnode.tag === "function") {
// 说明 vnode 描述的是组件
mountComponent(vnode, container);
}
}
vnode.tag 的类型是 字符串,说明它描述的是普通标签元素,此时调用 mountElement 函数完成渲染;
如果 vnode.tag 的类型是函数,则说明它描述的是组件,此时调用 mountComponent 函数完成渲染。其中 mountElement 函数与 renderer 函数 的内容一致:function mountElement(vnode, container) {
// 使用 vnode.tag 作为标签名称创建 DOM 元素
const el = document.createElement(vnode.tag);
// 遍历 vnode.props,将属性、事件添加到 DOM 元素
for (const key in vnode.props) {
if (/^on/.test(key)) {
// 如果 key 以字符串 on 开头,说明它是事件
el.addEventListener(
key.substr(2).toLowerCase(), // 事件名称 onClick ---> click
vnode.props[key] // 事件处理函数
);
}
}
// 处理 children
if (typeof vnode.children === "string") {
// 如果 children 是字符串,说明它是元素的文本子节点
el.appendChild(document.createTextNode(vnode.children));
} else if (Array.isArray(vnode.children)) {
// 递归地调用 renderer 函数渲染子节点,使用当前元素 el 作 为挂载点
vnode.children.forEach((child) => renderer(child, el));
}
// 将元素添加到挂载点下
container.appendChild(el);
}
mountComponent 函数是如何实现的: const vnode = {
tag: MyComponent
}
function mountComponent(vnode, container) {
// 调用组件函数,获取组件要渲染的内容(虚拟 DOM)
const subtree = vnode.tag();
// 递归地调用 renderer 渲染 subtree
renderer(subtree, container);
}
vnode.tag 函数,我们知道它其实就是组件函数本身,其返回值是虚拟 DOM,即组件要渲染的内容,这里我们称之为 subtree。
既然 subtree 也是虚拟 DOM,那么直接调用 renderer 函数完成渲染即可。// MyComponent 在这里是一个对象,而不是之前所说得的函数
const MyComponent = {
render() {
return {
tag: "div",
props: {
onClick: () => alert("hello"),
},
children: "click me",
};
},
};
render,其返回值代表组件要渲染的内容。renderer 渲染器以及 mountComponent 函数。function renderer(vnode, container) {
if (typeof vnode.tag === "string") {
mountElement(vnode, container);
} else if (typeof vnode.tag === "object") {
// 如果是对象,说明 vnode 描述的是组件
mountComponent(vnode, container);
}
}
typeof vnode.tag === 'function' 修改为 typeofvnode.tag === 'object'。mountComponent 函数:function mountComponent(vnode, container) {
// vnode.tag 是组件对象,调用它的 render 函数得到组件要渲染的内容(虚拟 DOM)
const subtree = vnode.tag.render();
// 递归地调用 renderer 渲染 subtree
renderer(subtree, container);
}
vnode.tag 是表达组件的对象,调用该对象的 render 函数得到组件要渲染的内容,也就是虚拟DOM。
【图神经网络论文整理】(八)—— Heterogeneous Graph Attention Network:HAN
STM32进行LVGL裸机移植
深度学习 神经网络(4)线性回归-Pytorch实现房价预测
沃通CA证书支持多所高校招投标文件电子签名
文化馆建筑方案设计原理及方案
产业互联网周报:我国数字经济规模超45万亿元;国家网信办就个人信息出境标准公开征求意见;阿里巴巴成立子公司瓴羊;国家发改委……...
Hadoop系列——Hadoop集群安装day2-1
Talk | 西安交通大学博士生赵子祥:基于先验知识指导的多模态图像融合算法研究
Worthington公司精氨酸酶:L-鸟氨酸的制备应用