vue3 对 vue2 有什么优势
1、diff算法的优化--静态标记(PatchFlag)
vue2中的虚拟dom是全量的对比(每个节点不论写死的还是动态的都会一层一层比较,这就浪费了大部分事件在对比静态节点上)
vue3编译模板时,动态节点做标记
标记分为不同的类型,如TEXT,PROPS
diff算法时,可以区分静态节点,以及不同类型的动态节点
vue3新增了静态标记(patchflag)与上次虚拟节点对比时,只对比带有patch flag的节点(动态数据所在的节点);可通过flag信息得知当前节点要对比的具体内容。
我们在Vue Template Explorer 上做测试,我们可以直观的看到对应的静态标记
上面的模板中前第一个段落是静态固定不变的,而第二个段落的内容绑定的HelloWorld属性,当HelloWorld改变的时候,Vue会生成新的虚拟DOM然后和旧的进行对比。
当视图更新时,只对动态节点部分进行diff运算,减少了资源的损耗。Patchflag是个枚举,取值为1代表这个元素的文本是动态绑定的,取值为2代表元素的class是动态绑定的。
算法对比从上图中可以看出 在vue3中如果是静态文本,更新时则不需要对比,只需要对只对动态节点部分进行diff运算
静态标记枚举了十几个类型,如下:
export const enum PatchFlags {
TEXT = 1,// 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
UNKEYED_FRAGMENT = 1 << 8, // 256 子节点没有 key 的 Fragment
NEED_PATCH = 1 << 9, // 512 表示只需要non-props修补的元素
DYNAMIC_SLOTS = 1 << 10, // 1024 动态的solt
DEV_ROOT_FRAGMENT = 1 << 11, //2048 表示仅因为用户在模板的根级别放置注释而创建的片段。 这是一个仅用于开发的标志,因为注释在生产中被剥离。
//以下两个是特殊标记
HOISTED = -1, // 表示已提升的静态vnode,更新时调过整个子树
BAIL = -2 // 指示差异算法应该退出优化模式
}
2、hoistStatic 静态提升
vue2无论元素是否参与更新,每次都会重新创建然后再渲染。
- 将静态节点的定义,提升到父作用域,缓存起来
- 多个相邻的静态节点,会被合并起来,一起定义,这样就会定义一次,避免多次定义
所以vue3对于不参与更新的元素,会做静态提升,只会被创建一次,在渲染时直接复用即可。
例如:利用Vue 3 Template Explorer,来直观的感受一下:
<div>
<div>共创1</div>
<div>共创2</div>
<div>{{name}}</div>
</div>
静态提升之前
export function render(...) {
return (
_openBlock(),
_createBlock('div', null, [
_createVNode('div', null, '共创1'),
_createVNode('div', null, '共创2'),
_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,
'共创2',
-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 之外,也就是我们说的静态提升。通过静态提升可以避免每次渲染的时候都要重新创建这些对象,从而大大提高了渲染效率。
3、cacheHandlers 事件侦听器缓存
vue2.x中,绑定事件每次触发都要重新生成全新的function去更新,cacheHandlers 是Vue3中提供的事件缓存对象,当 cacheHandlers 开启,会自动生成一个内联函数,同时生成一个静态节点。当事件再次触发时,只需从缓存中调用即可,无需再次更新。
默认情况下onClick会被视为动态绑定,所以每次都会追踪它的变化,但是同一个函数没必要追踪变化,直接缓存起来复用即可。
例如:下面我们同样是通过Vue 3 Template Explorer,来看一下事件监听器缓存的作用:
<div>
<div @click="todo">做点有趣的事</div>
</div>
该段 html 经过编译后变成我们下面的结构(未开启事件监听缓存):
export function render(...) {
return (_openBlock(),_createBlock('div', null, [
_createVNode('div',{ onClick: _ctx.todo}, '做点有趣的事', 8 /* PROPS */,
['onClick']),
])
)
}
当开启事件监听器缓存后:
export function render(...) {
return (_openBlock(),_createBlock('div', null, [
_createVNode('div',{
onClick: //开启监听后
_cache[1] || (_cache[1] = (...args) =>_ctx.todo(...args)),
},'做点有趣的事'),
])
)
}
可以对比开启事件监听缓存前后的代码,转换之后的代码, 大家可能还看不懂, 但是不要紧,我们只需要观察有没有静态标记即可,在Vue3的diff算法中, 只有有静态标记的才会进行比较, 才会进行追踪。
4、ssr渲染
Vue2 中也是有 SSR 渲染的,但是 Vue3 中的 SSR 渲染相对于 Vue2 来说,性能方面也有对应的提升。
当存在大量静态内容时,这些内容会被当作纯字符串推进一个 buffer 里面,即使存在动态的绑定,会通过模版插值潜入进去。这样会比通过虚拟 dmo 来渲染的快上很多。
当静态内容大到一个量级的时候,会用_createStaticVNode 方法在客户端去生成一个 static node,这些静态 node,会被直接 innerHtml,就不需要再创建对象,然后根据对象渲染。
5、更好的Ts支持
vue2不适合使用ts,原因在于vue2的Option API风格。options是个简单对象,而ts是一种类型系统、面向对象的语法。两者有点不匹配。
在vue2结合ts的具体实践中,要用 vue-class-component 强化 vue 组件,让 Script 支持 TypeScript 装饰器,用 vue-property-decorator 来增加更多结合 Vue 特性的装饰器,最终搞的ts的组件写法和js的组件写法差别挺大。
在vue3中,量身打造了defineComponent函数,使组件在ts下,更好的利用参数类型推断 。Composition API 代码风格中,比较有代表性的api就是 ref 和 reactive,也很好的支持了类型声明。
import { defineComponent, ref } from 'vue'
const Component = defineComponent({
props: {
success: { type: String },
student: {
type: Object as PropType<Student>,
required: true
}
},
setup() {
const year = ref(2020)
const month = ref<string | number>('9')
month.value = 9 // OK
const result = year.value.split('')
}
6、Compostion API: 组合API/注入API
能够更好的组织逻辑,封装逻辑,复用逻辑
传统的网页是html/css/javascript(结构/样式/逻辑)分离。vue通过组件化的方式,将联系紧密的结构/样式/逻辑放在一起,有利于代码的维护。compostion api更进一步,着力于JS(逻辑)部分,将逻辑相关的代码放在一起,这样更有利于代码的维护。
在vue2的组件内使用的是Option API风格(data/methods/mounted)来组织的代码,这样会让逻辑分散,举个例子就是我们完成一个计数器功能,要在data里声明变量,在methods定义响应函数,在mounted里初始化变量,如果在一个功能比较多、代码量比较大的组件里,你要维护这样一个功能,就需要在data/methods/mounted反复的切换到对应位置,然后进行代码的更改。
而在vue3中,使用setup函数。如下所示跟count相关的逻辑,都放到counter.js文件里,跟todo相关的逻辑放到todos.js里
import useCounter from './counter'
import useTodo from './todos'
setup(){
let { val, todos, addTodo } = useTodo()
let {count,add} = useCounter()
return {
val, todos, addTodo,
count,add,
}
7、更先进的组件
vue2是不允许这样写的,组件必须有一个跟节点,现在可以这样写,vue将为我们创建一个虚拟的Fragment节点。
<template>
<div>华为云享专家</div>
<div>全栈领域博主</div>
</template>
在Suspended-component完全渲染之前,备用内容会被显示出来。如果是异步组件,Suspense可以等待组件被下载,或者在设置函数中执行一些异步操作。
8、自定义渲染API
vue2.x项目架构对于weex(移动端跨平台方案)和myvue(小程序上使用)等渲染到不同平台不太友好,vue3.0推出了自定义渲染API解决了该问题。下面我们先看vue2和vue3的入口写法有哪些不同
vue2:
import Vue from 'vue'
import App from './App.vue'
new Vue({ => h(App)}).$mount('#app')
vue3:
const { createApp } from 'vue'
import App from "./src/App"
createApp(App).mount(('#app')
vue官方实现的 createApp 会给我们的 template 映射生成 html 代码,但是要是你不想渲染生成到 html ,而是要渲染生成到 canvas 之类的不是html的代码的时候,那就需要用到 Custom Renderer API 来定义自己的 render 渲染生成函数了。
import { createApp } from "./runtime-render";
import App from "./src/App"; // 根组件
createApp(App).mount('#app');
使用自定义渲染API,如weex和myvue这类方案的问题就得到了完美解决。只需重写createApp即可
9、按需编译,体积比vue2.x更小
框架的大小也会影响其性能。这是 Web 应用程序的唯一关注点,因为需要即时下载资源,在浏览器解析必要的 JavaScript 之前该应用程序是不可交互的。对于单页应用程序尤其如此。尽管 Vue 一直是相对轻量级的(Vue 2 的运行时大小压缩为 23 KB)。
在 Vue 3 中,通过将大多数全局 API 和内部帮助程序移至 ES 模块导出来,实现了这一目标。这使现代的打包工具可以静态分析模块依赖性并删除未使用的导出相关的代码。模板编译器还会生成友好的 Tree-shaking 代码,在模板中实际使用了该功能时才导入该功能的帮助程序,这样避免了引入多余的模块。
框架的某些部分永远不会 Tree-shaking,因为它们对于任何类型的应用都是必不可少的。我们将这些必不可少的部分的度量标准称为基准尺寸。尽管增加了许多新功能,但 Vue 3 的基准大小压缩后约为 10 KB,还不到 Vue 2 的一半
10、支持多根节点组件
Vue3 一个模板不再限制有多个根节点,(多个根节点上的 Attribute 继承) 需要显式定义 attribute 应该分布在哪里。否则控制台会给出警告提示。
在 Vue 3 中,组件现在正式支持多根节点组件,即片段!
在 2.x 中,不支持多根组件,当用户意外创建多根组件时会发出警告,因此,为了修复此错误,许多组件被包装在一个中。如下
<template>
<div>
<header>...</header>
<main>...</main>
<footer>...</footer>
</div>
</template>
在 3.x 中,组件现在可以有多个根节点!但是,这确实要求开发者明确定义属性应该分布在哪里。
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
11、Teleport
eleport 是一种能够将我们的模板移动到 DOM 中 Vue app 之外的其他位置的技术。
如果我们嵌套在 Vue 的某个组件内部,那么处理嵌套组件的定位、z-index 和样式就会变得很困难。使用Teleport 就可以方便的解决组件间 css 层级问题。
Teleport 的使用
[Teleport](https://v3.cn.vuejs.org/guide/teleport.html#%E4%B8%8E-vue-components-%E4%B8%80%E8%B5%B7%E4%BD%BF%E7%94%A8)
[teleport API](https://v3.cn.vuejs.org/api/built-in-components.html#teleport)