让给各位见笑了,这个只是记录的这段时间对《Vue.js 设计与实现》学习的记录,如果在某些方面不妥,还请您帮忙指出。谢谢😊
一、从这章节,我学到了哪些
- Vue有哪几种声明式描述UI
- 什么是渲染函数、渲染器、编译器,他们之间是怎样相辅相成,以及怎么实现的
- 组件本质是什么
- 虚拟DOM是什么
- 模板的工作原理
二、分别介绍一下上面的知识点
1. 声明式地描述UI
其实Vue3声明式地描述UI也是高度模板原生来写的,一下是对比
描述内容 | 声明式UI | 原生UI |
---|---|---|
DOM元素 | <com></com> | <div></div> |
属性描述 | <com id="app" :class="dynamicClass"></com> | <div id="app"></div> |
事件描述 | <com @click="handleClick"></com> | 需要js addEventListener |
层级关系 | <com><span></span></com> | <div><span></span></div> |
2.JavaScript对象描述
-
模板声明式:
<h1 @click="handler"><span></span></h1>
-
JavaScript对象描述:
const title = { // 标签 tag: 'h1', // 标签属性 props: { onClick: handler }, // 子节点 children: [ { tag: 'span' } ] }
-
模板式声明与JavaScript对象描述特点
- 模板式声明书写直观,简单
- JavaScript对象描灵活性高,比如动态的h1~h6标签,对象式更加灵活
-
虚拟DOM:
其实JavaScript对象描述就是虚拟DOM,只是这里描述的比较简单而已
3. 渲染函数、渲染器、编译器
-
渲染函数:h
import { h } from 'vue' export default { render() { return h('h1', { onclick: handler }) // 虚拟DOM } }
其实 h 函数返回的就是虚拟DOM
-
渲染器:将虚拟DOM渲染成真是的DOM
function render(vnode, container) { // 使用 vnode.tag 作为标签名称创建DOM元素 const el = document.createElement(vnode.tag); 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.appendChildren(document.createElement(vnode.children)); } else if (Array.isArray(vnode.children)) { // 递归调用 render 函数渲染子节点,使用当前元素 el 作为挂在节点 vnode.children.forEach((child) => render(child)); } // 将元素添加到挂载点下 container.appendChildren(el); }
渲染器的精髓:根据变更点,通过Diff算法找出并更新变更点
-
编译器:将模板编译为渲染函数
模板:<template> <div @click="handler"> click me </div> </template>
编译为渲染函数:
render(h) { return h("div", { onClick: handler }, "click me"); },
其实上面就是模板的工作原理:
模板 / 手写渲染函数 → 编译器编译成渲染函数 → 渲染函数生成虚拟DOM → 渲染器将虚拟DOM渲染成真是DOM并挂在到容器中(#app) -
相辅相成
组件实现依赖渲染器,模板编译依赖编译器,编译后的代码根据渲染器和虚拟DOM的设计决定的,各个模块之间是相互制约、相互关联共同构成一个有机整体。比如:编译器让渲染器提高性能。
编译器将 模板 / 渲染函数 编译成虚拟DOM的时候,标记哪些是动态属性patchFlag
,告知渲染器哪些是可能变更点,从而在Diff的时候降低工作量,提升性能。
4. 组件的本质
组件:一组虚拟DOM元素的封装,可以是返回的虚拟DOM,或者对象内含有一个函数返回虚拟DOM,被渲染函数渲染出来的称之为subtree
function render(vnode, container) {
if (typeof vnode.tag === "string") {
// 说明 vnode 描述的是标签元素
mountElement(vnode, container);
} else if (typeof vnode.tag === "function") {
// 说明vnode描述的是组件
mountComponent(vnode, container);
}
}
function mountElement(vnode, container) {
// 使用 vnode.tag 作为标签名称创建DOM元素
const el = document.createElement(vnode.tag);
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.appendChildren(document.createElement(vnode.children));
} else if (Array.isArray(vnode.children)) {
// 递归调用 render 函数渲染子节点,使用当前元素 el 作为挂在节点
vnode.children.forEach((child) => render(child));
}
// 将元素添加到挂载点下
container.appendChildren(el);
}
function mountComponent(vnode, container) {
// 调用组件函数,获取组件要渲染的内容(虚拟 DOM)
const subtree = vnode.tag();
// 递归调用 render 渲染 subtree
render(subtree, container);
}