Vue3 源码解析 01 --- Vue3 浅谈
前言
最近几个月一直忙于公司搬砖,导致都没有时间学习了。正好这段时间慢慢恢复了正常工作的作息,赶紧学习一下最新发布的 Vue3 框架。
为什么有 Vue3
首先贴一句官方文档的解释
Vue (pronounced /vjuː/, like view) is a progressive framework for building user interfaces.
按照官方的说法就是 Vue 是一个渐进式框架。至于什么是渐进式的框架这里就不多做介绍了,毕竟这是 Vue 从 1.x 到 3.x 一直秉承的理念(希望没用错词 🤔)。
这里其实要说的是 Vue3 要解决的问题。按照作者的说法,Vue 的重写其实是要解决下面几个问题:
- 使用新语言的特性,这其中就包括了 Proxy。Proxy 拥有比 defineProperty 更加强大的功能。后面我们会详细介绍。
- 解决体系架构存在的问题,例如:“Vue2 中使用模版编译器的编写方式使得正确的源映射支持非常具有挑战性”。
- Vue2 内部存在一些隐式的耦合,这会对代码库的修改带来更大的难度和隐患
Vue3 对比 Vue2 的优势
除了使用 TS 作为 Vue3 的设计开发语言之外,性能方面也是大大的优于 Vue2。
- 速度更快,Vue3 的 Performance 性能要比 Vue2 的快 1.2 ~ 2 倍
- 体积更小,Vue3 的 TreeShaking 的按需变异,让 Vue3 的体积比 Vue2 更小
- 组合 API,Vue3 的 Composition API(类似于 React Hooks ,只是类似而已),使我们的代码更加灵活且清晰。这也解决了我们上面提到的“源映射支持困难”的问题
- 暴露了自定义渲染 API
- 更先进的组件化
下面我们详细讨论一下 Vue3 是如何变快的:
diff 算法优化
Vue 中有一个独特的渲染策略:类似于 HTML 模版语法,Vue 将模版编译成渲染函数来返回虚拟 DOM 树。通过递归便利两个虚拟 DOM 树,并比较每个节点上的每个属性,来确定实际 DOM 哪些部分需要更新。 虽然经过现代 JS 引擎执行的高级优化之后,这种算法非常快速,但是在实际使用中 DOM 的更新仍然涉及到许多不必要的工作(具体表现在静态内容、只有少量动态模版的情况)。
这里有个需要注意的点就是,虽然 Vue2 在某种程度上通过跳过静态子树进行优化,但是过于简单的编译器体系架构师的更高级的优化很难实现。因此 Vue3 对静态优化实现更为细致和高效。
DOM 树级别,Vue3 通过分析模版,如果在没有动态改变节点结构的模版指令(例如:v-if 和 v-for)的情况下,节点结构会保持相对的静态。因此这些模版其实是由这些结构指令分割的嵌套“静态块”,每个块中的节点结构保持完全静态。当我们再次更新时,这写静态模块则不需要再次递归遍历,而是可以在一个平面数组中跟踪,这样就省去了大量的性能开销(据作者说是可以减少一个数量级)。
解释的比较繁琐,大家可以简单的认为虽然是动态模版,但是里面有静态的部分,Vue3 要做的就是追踪这些静态节点,避免之后再次遍历
hoistStatic 静态提升
Vue3 中的编译器还会检测模版中的静态节点、子树甚至数据对象,并在生成的代码中将他们提升到渲染函数之外。这样可以避免在每次渲染时重新创建这些对象,从而大大提高内存使用率并减少垃圾回收的频率。 这样解释的话有点抽象,下面我们利用Vue 3 Template Explorer,来直观的感受一下:
<!--我们的html代码-->
<div>
<div>静态1</div>
<div>静态1</div>
<div>{{name}}</div>
</div>
//静态提升之前
export function render(...) {
return (
_openBlock(),
_createBlock('div', null, [
_createVNode('div', null, '静态1'),
_createVNode('div', null, '静态1'),
_createVNode(
'div',
null,
_toDisplayString(_ctx.name),
1 /* TEXT */
),
])
)
}
//静态提升之后
const _hoisted_1 = /*#__PURE__*/ _createVNode(
'div',
null,
'静态1',
-1 /* HOISTED */
)
const _hoisted_2 = /*#__PURE__*/ _createVNode(
'div',
null,
'静态1',
-1 /* HOISTED */
)
export function render(...) {
return (
_openBlock(),
_createBlock('div', null, [
_hoisted_1,
_hoisted_2,
_createVNode(
'div',
null,
_toDisplayString(_ctx.name),
1 /* TEXT */
),
])
)
}
从上面的代码我们看出,_hoisted_1 和_hoisted_2 两个方法被提升到了渲染函数 render 之外,这就是我们所说的静态提升。通过静态提升避免每次渲染的时候都要重新创建这些对象,从而大大提高了渲染效率。
追踪元素类别
在元素层面,Vue3 编译器还根据需要执行的更新类型,给每个具体动态绑定的元素生成一个优化标志。具有动态类绑定和许多静态属性的元素将收到一个标志。运行时将获取这些提示并采用专用的快速路径。 这里贴上 Vue3 源码里面 Flag 的类型
export const enum PatchFlags {
TEXT = 1,//文本类型
CLASS = 1 << 1,//动态class
STYLE = 1 << 2,//样式
PROPS = 1 << 3,//动态属性
FULL_PROPS = 1 << 4,//具有动态key属性,当key改变时需要进行完整的diff
HYDRATE_EVENTS = 1 << 5,//带有监听事件的节点
STABLE_FRAGMENT = 1 << 6,//一个不会改变子节点顺序的fragment
KEYED_FRAGMENT = 1 << 7,//带有key属性的fragment活部分字节点有key
UNKEYED_FRAGMENT = 1 << 8,//字节点没有key的fragment
NEED_PATCH = 1 << 9,//一个字节点置灰进行非props比较
DYNAMIC_SLOTS = 1 << 10,//动态插槽
HOISTED = -1,//静态节点
BAIL = -2//特殊处理
}
cacheHandlers 事件监听器缓存
默认情况下 onClick 会被视为动态绑定,所以每次都会追踪他的变化。但是函数都是那同一个函数所以没必要追踪更新,直接缓存起来复用就好啦,这就是我们要 说的事件监听器缓存。 下面我们同样是通过Vue 3 Template Explorer,来看一下事件监听器缓存的作用:
<!--我们的结构-->
<div>
<div @click="doSomething">点我</div>
</div>
该段 html 经过编译后变成我们下面的结构(未开启事件监听缓存):
//未开启事件缓存监听
export function render(...) {
return (
_openBlock(),
_createBlock('div', null, [
_createVNode(
'div',
{ onClick: _ctx.doSomething },
'点我',
8 /* PROPS */,
['onClick']
),
])
)
}
当我们开启事件监听器缓存后:
//开启事件缓存监听
export function render(...) {
return (
_openBlock(),
_createBlock('div', null, [
_createVNode(
'div',
{
onClick:
//开启监听后
_cache[1] ||
(_cache[1] = (...args) =>
_ctx.doSomething(...args)),
},
'点我'
),
])
)
}
我们可以对比开启事件监听缓存前后的代码,其中比较重要的就是,开启事件监听缓存之后 PatchFlag 不存在了,也就意味着,当我们开启事件监听缓存之后,取消追踪变化了。
SSR 渲染
Vue2 中也是有 SSR 渲染的,但是 Vue3 中的 SSR 渲染相对于 Vue2 来说,性能方面也有对应的提升。
- 当存在大量静态内容的时候,这些内容会被当作纯字符串推进一个 buffer 里面,即使存在动态的绑定,会通过模版插值潜入进去。这样会比通过虚拟 dmo 来渲染的快上很多。
- 当静态内容大到一个量级的时候,会用_createStaticVNode 方法在客户端去生成一个 static node,这些静态 node,会被直接 innerHtml,直接越过了创建对象的过程
这里说的比较笼统,后面的文章我们可以从源码层面详细解析一下。
使用 Vite 创建 Vue3
Vue3 中使用了 Vite 而不是之前的 webpack 来创建项目。Vue3 的作者意图使用 Vite 取代 webpack,同时 Vite 的性能也要大大优于 webpack
什么事 Vite
- Vite 是 Vue3 创建项目的工具,作者意图使用它取代 webpack
- 实现原理是利用 ES6 的 import 会发送请求去加载文件的特性,拦截这些请求,做一些预编译,相对于 webpack 来说省去了冗长的打包时间
使用 Vite
- 安装 Vite npm install -g create-vite-app
- 利用 Vite 创建 Vue3 项目 create -vite-app <projectName>
- 安装依赖运行项目 cd <projectName> npm install npm run dev
总结
到这里,我们只是简单的认识了一下 Vue3 的特点,甚至于没有提及其具体的使用和语法,不过对于前端三大框架之一来说,有个全面的认知才是一件有意思的事情嘛。后面我们会循序渐进的学习 Vue3 的语法和源码设计理念,期望和大家共同进步