最近都在忙着搬砖了,没意思没意思。这几天抽空继续学点新东西。Vue3出了快两个月了,现在才补哈哈哈。
Vue3亮点
Performance
:性能比Vue 2.x快1.2~2倍Tree shaking support
:按需编译,体积比Vue2.x更小Composition API
: 组合API(类似React Hooks)Better TypeScript support
:更好的 Ts 支持Custom Renderer API
:暴露了自定义渲染APIFragment, Teleport(Protal), Suspense
:更先进的组件
Vue3性能提升的原因
diff算法优化
vue2的diff算法
之前学过,在vue中根据_update
将虚拟DOM转为真实DOM并更新视图,其中主要调用了__patch__
方法,它会将新老VNode
进行比较,然后根据比较结果最小单位的修改视图。__patch
的核心在于diff
算法。
diff
通过同层的树节点进行比较,所以时间复杂度只有O(n)
:
这里不讨论diff
的具体实现,只需要知道vue2
中diff
的比较是同层树节点进行比较。
假设有这么一段代码:
<template>
<div>
<p>我是固定的</p>
<p>{{message}}</p>
</div>
</template>
那么每次message
更新,就会去逐层比较,但是其实最外层的div
和第一个子元素p
是固定的,是不会改变的。vue3改良了diff
的算法比较。
vue3的diff算法
vue3新增了静态标记(PatchFlag
),每次比较的时候只会比较带有patch flag
的节点,并且通过pacth flag
的类型得知要比较的内容。
// pacth flag 值与类型的对应关系
export const enum PatchFlags {
TEXT = 1,// 动态文本节点
CLASS = 1 << 1, // 2 // 动态 class
STYLE = 1 << 2, // 4 // 动态 style
PROPS = 1 << 3, // 8 // 动态属性,但不包含类名和样式
FULL_PROPS = 1 << 4, // 16 // 具有动态 key 属性,当 key 改变时,需要进行完整的 diff 比较。
HYDRATE_EVENTS = 1 << 5, // 32 // 带有监听事件的节点
STABLE_FRAGMENT = 1 << 6, // 64 // 一个不会改变子节点顺序的 fragment
KEYED_FRAGMENT = 1 << 7, // 128 // 带有 key 属性的 fragment 或部分子字节有 key
UNKEYED_FRAGMENT = 1 << 8, // 256 // 子节点没有 key 的 fragment
NEED_PATCH = 1 << 9, // 512 // 一个节点只会进行非 props 比较
DYNAMIC_SLOTS = 1 << 10, // 1024 // 动态 slot
HOISTED = -1, // 静态节点
// 指示在 diff 过程应该要退出优化模式
BAIL = -2
}
将上述列子,复制到 在线源码转换上看,转为如下源码:
import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("template", null, [
_createVNode("div", null, [
_createVNode("p", null, "我是固定的"),
_createVNode("p", null, _toDisplayString(_ctx.message), 1 /* TEXT */)
])
]))
}
其中只有<p>{{message}}</p>
是带有pacth flag
标记的(1代表动态文本节点),这样每次更新的时候,只会比较message
是否发生改变,而不会比较其他节点,提升了性能。
hoistStatic 静态提升
Vue2中无论元素是否参与更新, 每次都会重新创建, 然后再渲染,而Vue3中对于不参与更新的元素, 会做静态提升, 只会被创建一次, 在渲染时直接复用即可。还是引用上个例子,比较打开hoistStatic
可以看到我是固定的
这个p
节点在不同情况下是否被提升。
cacheHandlers 事件侦听器缓存
<template>
<div>
<button @click="handleChange">改变message</button>
</div>
</template>
在不使用cacheHandlers
下,@click
会被视为动态绑定, 因为绑定的函数可能会被改变,所以每次都会去追踪它的变化:
_createVNode("div", null, [
_createVNode("button", { onClick: _ctx.handleChange }, "改变message", 8 /* PROPS */, ["onClick"])
])
开启cacheHandlers
,在第一次渲染时会自动生成一个内联的函数,在内联函数里面引用当前的fn,然后把内联函数cache起来,后续的更新会从缓存中读同一个函数,因为是同一个函数,也就没有追踪变化的必要
_createVNode("div", null, [
_createVNode("button", {
onClick: _cache[1] || (_cache[1] = (...args) => (_ctx.handleChange(...args)))
}, "改变message")
])
ssr渲染
<template>
<div>
<p>我是固定的</p>
<p>我是固定的</p>
<p>我是固定的</p>
<p>我是固定的</p>
<p>我是固定的</p>
<p>我是固定的</p>
<p>我是固定的</p>
<p>我是固定的</p>
<p>我是固定的</p>
</div>
</template>
// 无开启
import { createVNode as _createVNode, openBlock as _openBlock, createBlock as _createBlock } from "vue"
const _hoisted_1 = /*#__PURE__*/_createVNode("div", null, [
/*#__PURE__*/_createVNode("p", null, "我是固定的"),
/*#__PURE__*/_createVNode("p", null, "我是固定的"),
/*#__PURE__*/_createVNode("p", null, "我是固定的"),
/*#__PURE__*/_createVNode("p", null, "我是固定的"),
/*#__PURE__*/_createVNode("p", null, "我是固定的"),
/*#__PURE__*/_createVNode("p", null, "我是固定的"),
/*#__PURE__*/_createVNode("p", null, "我是固定的"),
/*#__PURE__*/_createVNode("p", null, "我是固定的"),
/*#__PURE__*/_createVNode("p", null, "我是固定的")
], -1 /* HOISTED */)
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createBlock("template", null, [
_hoisted_1
]))
}
// 开启
import { mergeProps as _mergeProps } from "vue"
import { ssrRenderAttrs as _ssrRenderAttrs } from "@vue/server-renderer"
export function ssrRender(_ctx, _push, _parent, _attrs, $props, $setup, $data, $options) {
const _cssVars = { style: { color: _ctx.color }}
_push(`<template${_ssrRenderAttrs(_mergeProps(_attrs, _cssVars))}><div><p>我是固定的</p><p>我是固定的</p><p>我是固定的</p><p>我是固定的</p><p>我是固定的</p><p>我是固定的</p><p>我是固定的</p><p>我是固定的</p><p>我是固定的</p></div></template>`)
}
开启SSR
之后发现,当有大量静态的内容时候,这些内容会被当做纯字符串推进一个buffer里面,即使存在动态的绑定,会通过模板插值嵌入进去。这样会比通过虚拟dmo来渲染的快上很多很多。当静态内容大到一定量级时候,会用_createStaticVNode方法在客户端去生成一个static node,这些静态node,会被直接innerHtml,就不需要创建对象,然后根据对象渲染。
Vue3上手
创建Vue3的3种方式
- Vue-CLI
- Webpack
- Vite
ESM
script module
最大的特点就是在浏览器端使用 export
、import
的方式导入和导出模块,目前主流的浏览器都已经支持。使用的时候我们只要在srcipt
标签写上type="module"
浏览器会把这段内联 script 或者外链 script 认为是 ECMAScript
模块,浏览器将对其内部的 import
引用发起 http 请求获取模块内容。
vite
它是一个由原生ESM驱动的Web
开发构建工具,在开发环境下基于浏览器原生ES imports
开发,在生产环境下基于Rollup
打包。
冷启动快
之前vue
使用的是webpack
,webpack
之类的打包工具是为了在浏览器里加载各模块,会组装各模块;所以冷启动比较慢;而vite
利用原生浏览器支持模块化导入的特性,省略了对模块的组装,即不需要生成bundle
,所以冷启动比较快。
按需编译
webpack
会将各个模块打包进bundle
里,但是整个过程是静态的,即不管某个模块的代码是否执行到,都会被打包进bundel
中,所以项目越大,打包后bundle
越大。而ESM
就是按需加载模块的,所以实现了真正的按需编译。
即时更新
vite碰见import
会发生http
请求去加载文件,vite
会拦截这些请求,然后做一些预编译,省去了webpack
冗长的打包事件,所以热替换功能很快。
总结:vite
会拦截浏览器对模块的请求然后返回处理后的结果,import
一下需要的包实现了真正的按需加载,由于不会将所有的包进行打包处理所以冷启动比较快,且每次更新也因为省去了这些打包流程所以更新的很快。
不过也是有缺点的: 必须用import,不能用require引npm包,且包必须支持import/umd
(如path、mockjs
会报错缺default
)。
使用vite开发
npm install -g create-vite-app
create-vite-app projectName
cd projectName
npm install
npm run dev