Vue3.0 性能优化及新特性深度解析

6,393 阅读5分钟

最近,Vue作者尤雨溪在B站的演讲中分享了Vue3.0的六大亮点:

  • 性能
  • Tree-shaking 支持
  • Composition API
  • Fragment、Teleport、Suspense
  • 更好的 TS 支持
  • 自定义渲染API

编译时对VDom的性能优化

PatchFlag

首先看下面这个案例,模版中有三个P标签,其中只有最后一个P标签的TEXT部分是动态的

在之前的VDOM中,如果msg值发生改变,整个模版中的所有元素都需要重新渲染。但在Vue3.0中,在这个模版编译时,编译器会在动态标签末尾加上 /* Text*/ PatchFlag。只能带patchFlag 的 Node 才被认为是动态的元素,会被追踪属性的修改。并且 PatchFlag 会标识动态的属性类型有哪些,比如这里 的TEXT 表示只有节点中的文字是动态的。

每一个Block中的节点,就算很深,也是直接跟Block一层绑定的,可以直接跳转到动态节点而不需要逐个逐层遍历。

既有VDOM的灵活性,又有性能保证。

hoistStatic 静态节点提升

当使用hoistStatic时,所有 静态的节点都被提升到render方法之外。这意味着,他们只会在应用启动的时候被创建一次,而后随着每次的渲染被不停的复用。

在大型应用中对于内存有很大的优化。

cacheHandler 事件监听缓存

正常情况下,当绑定一个事件:

<div>
  <p @click="handleClick">静态代码</p>
</div>

模版会被编译为

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", { onClick: _ctx.handleClick }, "静态代码", 8 /* PROPS */, ["onClick"])
  ]))
}

其中事件会每次从全局上下文中获取。而当开启了cacheHandler之后

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("p", {
      onClick: _cache[1] || (_cache[1] = ($event, ...args) => (_ctx.handleClick($event, ...args)))
    }, "静态代码")
  ]))
}

编辑器会为你动态创建一个内联函数,内联函数里面再去饮用当前组件上最新的handler。之后编辑器会将内联函数缓存。每次重新渲染时如果事件处理器没有变,就会使用缓存中的事件处理而不会重新获取事件处理器。这个节点就可以被看作是一个静态的节点。这种优化更大的作用在于当其作用域组件时,之前每次重新渲染都会导致组件的重新渲染,在通过handler缓存之后,不会导致组件的重新渲染了。

SSR 服务端渲染

当开启SSR了之后,如果我们模版中有一些静态标签,这些静态标签会被直接转化成文本。

其中的动态绑定依然是一个单独的字符串内嵌进去。这个性能肯定比React 转成VDOM在专为HTML快很多。

StaticNode 静态节点

刚才提到在SSR中静态的节点会被转化为纯字符串。如果在客户端,当静态节点嵌套足够多的时候,VUE编译器也会将VDOM转化为纯字符串的HTML。即 StaticNode。

通过这些操作,我们可以看下,跟vue2比可以快一倍以上,内存占用可以小一倍以上。

Tree Shaking

因为ES6模块是静态引用的,所以我们可以在编译时正确的判断到底加载了哪些代码。对代码全局做一个分析,找到那些没用被用到的模块、函数、变量,并把这些去掉。

当使用一个 bundle (webpack etc.)的时候,默认会加上 TreeShaking。Vue 3.0 中没有被用到的模块可以不被打包到编译后的文件中,被 TreeShake 掉。当只有一个HelloWorld的时候 Vue3打包后 13.5kb。所有的组件全部加载进来时是 22.5kb

Composition API

随着Vue组件的增大,组件内代码变得越来越难以理解和维护。其中的一些可以复用的代码很难被抽离出来。同时 Vue2.0还缺少 TS支持。在Vue2中,逻辑概念(功能)被管理在组件中,但是功能和组件并不是一对一关系。一个功能可以被多个组件使用同时一个组件可以有多个功能。在Vue中,一个功能可能需要依赖多个Options(components、props、data、computed、methods及生命周期方法)。

在 Composition API中提供可 setup 方法。以一个有搜索功能和 排序功能组件为例:

<script>
export default {
    setup() {
    
    }
}

function useSearch() {
    return { 
    ...useSearch(), 
    ...useSorting()
    }
}

function useSorting() {
    
}

</script>

Vue2 中的代码复用

Mixin

在Vue2中有几种方式可以复用代码,其中之一就是 Mixins。

  • Mixins可以实现组织功能
  • 容易发生冲突
  • 很难说明依赖关系
  • 代码不容易复用

Mixin 工厂

  • 可以方便复用
  • 明确的依赖关系
  • 弱命名空间
  • 隐性的属性添加

Scoped Slots

  • 解决了 Mixin 的问题
  • 增加了层级关系导致更难以理解
  • 很多配置信息
  • 灵活性更少
  • 性能较差

核心 API

  • reactive
  • ref
  • computed
  • readonly
  • watchEffect
  • watch
  • Lifecycle Hooks

Fragments

Vue3中不在要求模版的跟节点必须是只能有一个节点。跟节点和和render函数返回的可以是纯文字、数组、单个节点,如果是数组,会自动转化为 Fragments。

Teleport

对标 React Portal。可以做一些关于响应式的设计,如果屏幕宽度比较宽的时候,加入某些元素,屏幕变窄后移除。

Suspense

等待嵌套的异步依赖。再把一个嵌套的组件树渲染到页面上之前,先在内存中进行渲染,并记录所有的存在异步依赖的组件。只有所有的异步依赖全部被resolve之后,才会把整个书渲染到dom中。当你的组件中有一个 async的 setup函数,这个组件可以被看作是一个Async Component,只有当这个组件被Resolve之后,再把整个树渲染出来

  • async setup()
  • Async Component

Typescript

Vue3源码使用 TS重写,但不意味着vue3的项目也要使用TS。但Vue3会对 TS有更好的支持

  • 支持 TSX
  • 支持 Class component
  • 代码会变大一些

参考资料