前言
最近偶尔刷到Vue.js 设计与实现这本书的推荐, 发现是尤雨溪推荐的, Vue.js 官方团队成员霍春阳写的一本书.
看了书本简介(以下),刚好可以深入研究一下 Vue.js 3, 就入手了。
本书基于 Vue.js 3,从规范出发,以源码为基础,并结合大量直观的配图,循序渐进地讲解 Vue.js 中各个功能模块的实现,细致剖析框架设计原理。全书共18章,分为六篇,主要内容包括:框架设计概览、响应系统、渲染器、组件化、编译器和服务端渲染等。通过阅读本书,对 Vue.js 2/3具有上手经验的开发人员能够进一步理解 Vue.js 框架的实现细节,没有Vue.js使用经验但对框架设计感兴趣的前端开发人员,能够快速掌握 Vue.js 的设计原理。
这读书简记只是自我的读书笔记总结与汇总,方便自己日后回忆翻看,所以内容可能比较随意简洁,毕竟详细内容可以看原书。
接上一节
2. 框架设计的核心要素
框架设计的思考:
- 提供构建产物
- 产物模块格式
- 错误信息提示
- 开发版与生产版区别
- 热更新
- 打包体积
- 提升用户的开发体验,提供友好的错误信息提示, 准确的定位错误位置
- 控制框架代码的体积,通过 rollup.js 插件预定义,
__DEV__常量控制生产环境与开发环境代码。达到开发环境中为用户提供友好的警告信息的同时,不会增加生产环境代码的体积。 - 框架要做到良好的 Tree-Shaking,实现 Tree-Shaking 前提是模块必须是 ESM(ES Module), 因为 Tree-Shaking 依赖 ESM 的静态结构。
- 框架应该输出怎样的构建产物
- 特性开关
- 错误处理
- 良好的 TypeScript 类型支持
3. Vue.js 3 的设计思路
Vue.js 3 是声明式 UI 框架
- 模版描述
<div></div>
<div @click="dynamicID"></div>
- JS 描述
const title = {
tag: 'h1',
props: {
onclick: handler
},
children: [
{tag: 'span'}
]
}
渲染器
渲染器的作用就是把虚拟 DOM 渲染为真实 DOM
graph TD
虚拟DOM --> 渲染器 --> 真实DOM
function renderer(vnode, continer) {
// 使用 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.substring(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))
}
// 将元素添加到挂载点
continer.appendChild(el)
}
这里 renderer 函数接收如下二个参数
- vnode:虚拟 DOM 对象
- container:一个真实的 DOM 元素, 作为挂载点, 渲染器会把虚拟 DOM 渲染到这下面
renderer(vnode, document.body) // body 作为挂载点
renderer 思路
- 创建元素
- 为元素添加属性和事件
- 处理 children
const vnode = {
tag: 'div',
props: {
onclick: ()=>alert('hello')
},
children: 'click again' // 从 click me 改成 click again
}
上面小小的修改, 渲染器只需要精确定的找到 vnode 对象的变更点并更新变更内容.
组件的本质
虚拟 DOM 除了可以描述真实 DOM 外,还可以描述组件。
组件就是一组 DOM 元素的封装
组件 MyConpnent 的返回值就是代表组件渲染的内容
const MyConpnent = {
tag: 'div',
props: {
onclick: () => alert('hello')
},
children: 'click me'
}
定义虚拟 DOM 描述组件 MyConpnent, 虚拟 DOM 对象中的 tag 属性存储组件函数
const vnode = {
tag: MyConpnent
}
渲染组件
function renderer(vnode, continer) {
if (typeof vnode.tag === 'string') {
// 说明 vnode 描述的是标签元素
mountElement(vnode, continer)
} else if (vnode.tag === 'function')) {
// 说明 vnode 描述的是组件
mountElement(vnode, continer)
}
}
mountElement 递归的调用 renderer 渲染
function mountElement(vnode, continer) {
// 调用组件函数, 获取组件要渲染内容
const subtree = vnode.tag()
// 递归的调用 renderer 渲染 subtree
renderer(subtree, continer)
}
上面描述组件的时候是组件函数。但同时组件就一定时函数吗?当然不是,组件还可以是对象来表达。
// MyConpnent 是一个对象
const MyConpnent = {
render() {
return{
tag: 'div',
props: {
onclick: () => alert('hello')
},
children: 'click me'
}
}
}
修改渲染器判断条件
function renderer(vnode, continer) {
if (typeof vnode.tag === 'string') {
mountElement(vnode, continer)
} else if (vnode.tag === 'object')) { // 如果是对象, 说明 vnode 描述的是组件
mountElement(vnode, continer)
}
}
接着修改 mountElement 函数
function mountElement(vnode, continer) {
// vnode.tag 是组件对象, 调用它的 render 函数得到组件要渲染内容(虚拟 DOM)
const subtree = vnode.tag.render()
// 递归的调用 renderer 渲染 subtree
renderer(subtree, continer)
}
模版的工作原理
无论是手写虚拟 DOM (渲染函数) 还是使用模板,都属于声明式地描述 UI,并且 Vue 同时支持这两种 UI 表示方式。那么模板是怎么工作的呢? ---> 编译器 编译器与渲染器一样, 只是一段程序而已, 不过工作内容不同.
编译器作用是将模板编译为渲染函数
<div @click='handler'>
click me
</div>
模板--->渲染函数
render() { return h('div',{onClick:handler},'click me') }
.vue 文件就是一个组件
<template>
<div @click='handler'>
click me
</div>
</template>
<script>
export default {
data() {/*···*/},
methods:{/*···*/},
}
</script>
<template>标签里的内容就是模板内容,编译器会把模板内容编译成渲染函数并添加到<script>标签块的组件对象上,所以最终在浏览器运行的代码是:
export default {
data() {/*···*/},
methods: {
handler: () => {/*···*/}
},
render() {
return h('div', { onClick: handler }, 'click me')
}
}
对于组件来说, 它渲染的内容最终都是通过渲染函数产生的, 然后渲染器把渲染函数返回 虚拟 DOM 渲染为真实 DOM. 这就是模板工作原理, 也是 Vue.js 渲染页面的流程.
Vue.js 是各个模块组成的有机整体
Vue.js 模板会被一个叫作编译器的程序编译为渲染函数, 最后, 编译器、渲染器都是 Vue.js 的核心组成部分, 它们共同构成一个有机的整体, 不同模块之间互相配合, 进一步提升框架性能.
未完待续