阅读 957

Vue3.0 Beta笔记(侧重Performance提升原因和Composition API)

前言

  昨晚(北京时间2020年4月22日),Vue作者Evan You(尤雨溪)在前端圈的B站直播间分享了关于'Vue3.0 Beta'的一些新的特性和进展。(在此附附上掘金录播链接:juejin.im/e/vue-3) 

   本文,是个人记录的一些笔记,更关注与侧重Performance提升原因和Composition API,这两部分记录的比较详细,相当于是下方连接的一些补充,欢迎大家留言补充和探讨。

参考链接:抄笔记:尤雨溪在Vue3.0 Beta直播里聊到了这些…

一、RFCs(Request For Comments)


    尤大在直播开头提到,所有的Vue3大的的改动都是通过 Vue3-RFCs GitHub仓库上和大家进行讨论,所有进度和改动细节都能在其中看到,不需要再获取别人的二手信息了。如果大家想要深入了解可以点击上面的连接。(PS:看的真的会很累,讨论地特别细。)

二、六大亮点


  1. Performance:性能更比Vue 2.0强。(update 性能提高1.3-2倍和ssr服务端渲染速度快2-3倍,基于bechmark)
  2. Tree shaking support:可以将无用模块“剪枝”。
  3. Composition API:组合API 相对于 Vue2.x Options API(后面细讲)
  4. Fragment, Teleport, Suspense
  5. Better TypeScript support:更优秀的Ts支持
  6. Custom Renderer API:暴露了自定义渲染API


1.Performance提升的原因(视频中通过以下Demo展示)

可以通过点击vue-next-template-explorer进入视频中模板编制的Playgruond。其永远和vue3.0 github上master最新的commit保持一致

(1)模板编译的优化


代码:

<div>
  <span/>
  <span>{{ msg }}</span>
</div>复制代码

将会被编译成以下模样: 

import { createVNode as _createVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createBlock as _createBlock } from "vue"

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [ 
    _createVNode("span", null, "static"),
    _createVNode("span", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)
  ]))
}

// Check the console for the AST
复制代码

根结点div将会被编译成Block

动态绑定msg属性的span,编译后_createVNode会生成PacthFlag(相当于编译时生成一个hint),JS runtime在运行的时候,会知道div是一个block,只会对带有PacthFlag的结点进行真正的追踪。在真正的更新的时候,会直接跳到该结点,比较该结点文字的变化。不需要去关注其他属性和绑定的变化。

PatchFlags枚举定义

export const enum PatchFlags {
  
  TEXT = 1,// 表示具有动态textContent的元素
  CLASS = 1 << 1,  // 表示有动态Class的元素
  STYLE = 1 << 2,  // 表示动态样式(静态如style="color: red",也会提升至动态)
  PROPS = 1 << 3,  // 表示具有非类/样式动态道具的元素。
  FULL_PROPS = 1 << 4,  // 表示带有动态键的道具的元素,与上面三种相斥
  HYDRATE_EVENTS = 1 << 5,  // 表示带有事件监听器的元素
  STABLE_FRAGMENT = 1 << 6,   // 表示其子顺序不变的片段(没懂)。 
  KEYED_FRAGMENT = 1 << 7, // 表示带有键控或部分键控子元素的片段。
  UNKEYED_FRAGMENT = 1 << 8, // 表示带有无key绑定的片段
  NEED_PATCH = 1 << 9,   // 表示只需要非属性补丁的元素,例如ref或hooks
  DYNAMIC_SLOTS = 1 << 10,  // 表示具有动态插槽的元素
  // 特殊 FLAGS -------------------------------------------------------------
  HOISTED = -1,  // 特殊标志是负整数表示永远不会用作diff,只需检查 patchFlag === FLAG.
  BAIL = -2 // 一个特殊的标志,指代差异算法(没懂)
}
复制代码

更具体的例子:

加上一大堆的静态内容:


在默认的Diff的算法下,会把所有的静态的span 都检查一遍,而且每个span都要看新的Props和旧的有没有变化,虽然说JS做这写效率很高,但是当所需要的更新的结点量很大时,不可避免地会浪费很多的时间。

  在Vue 3.0的Diff的算法中,只需在Block中寻找带PacthFlag的结点,只要把这些结点检查一遍就行了,这样就解决的传统Diff算法中最耗时最浪费性能的部分。


无论层级嵌套多深,它的动态节点都直接与Block根节点绑定,无需再去遍历静态节点


如果存在一个动态的绑定和静态绑定:


我们在Diff时,只会关注id是否变化,不会关注class的变化。

PatchFlag 变成了9 /* TEXT, PROPS */, ["id"],它会告知我们不光有TEXT变化,还有PROPS变化(id)

这样既跳出了virtual dom性能的瓶颈,又保留了可以手写render的灵活性。 等于是:既有react的灵活性,又有基于模板的性能保证。


react是否能在JSX做这样的东西呢?在简单的情况下可以做,因为JSXJS的语法比模板本身的语法要灵活的多。

所说的简单的情况:

hostStatic(把静态的结点提升)


  静态的结点被拿到渲染函数外,在应用启动的时候被创建一次,这些虚拟结点就会在每次渲染的时候被复用。优化大型项目的内存占用,不用每次渲染都创建这些结点了。


(2) 添加事件监听缓存:cacheHandlers

关闭cacheHandlers


export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("span", { onClick: _ctx.onClick }, _toDisplayString(_ctx.msg), 9 /* TEXT, PROPS */, ["onClick"])
  ]))
}复制代码

开启cacheHandlers


cache[1],会自动生成并缓存一个内联函数,“神奇”的变为一个静态节点。

export function render(_ctx, _cache) {
  return (_openBlock(), _createBlock("div", null, [
    _createVNode("span", {
      onClick: _cache[1] || (_cache[1] = $event => (_ctx.onClick($event)))
    }, _toDisplayString(_ctx.msg), 1 /* TEXT */)
  ]))
}复制代码

并且支持手写内联函数:

<div>
  <span @click="()=>foo()">
    {{msg}}
  </span>
</div>复制代码

(3)SSR(server side render服务端渲染)优化

静态内容会直接当做纯字符串推进一个buffer里去了。


即使存在动态绑定,依然尽可能地做成字符串。

_ssrRenderAttr("id",_ctx.foo)复制代码


当静态结点数量超过一定阈值时,启用这样一个优化。单独的创建一个div,将其设置成innerHTML。

  比React做成一个Virtual Dom 再去渲染出来,快上一个量级。服务端渲染的性能,完全不在一个层面上。

整体上,比Vue 2.x 内存占用少一半以上,总体速度快一倍以上。


2.Composition API(组合) 和 Options API的对比

组件小的时候,用不同的Options比如methods、compute、data、props等这样分类比较清晰。大型组件中,大量的Options聚在一起。同一个组件可能有多个逻辑关注点,当使用Options API时,每一个关注点都有自己的Options,如下图每一个颜色代表不同的逻辑关注点之间的代码。当修改一个逻辑关注点时,就要在一个文件不断地切换和寻找。

                                                


如要用切分这些逻辑点呢?有时候,不好切分,如果用minxin又会导致命名空间冲突。

Composition API给了一个很好的机制去解决这样的问题,所有某一个逻辑关注点(功能)相关的代码全都放在一个函数里,当需要去修改一个功能时,就不再需要在一个文件中跳来跳去。


当需要复用的时候,就只需要把这个函数提取出去。然后在另一个组件中引入,这个功能就变得可复用了,Composition API使得组件复用变得更加灵活了。


另一方面,Composition API会有更好的类型的支持,因为都是一些函数,在调用函数时,自然所有的类型就被推导出来了。不像OptionsAPI所有的东西使用this。同时,Composition API的可压缩性会更好一些。以上就是Composition API引入的理由。


具体地Composition API如何使用可以同过这个网站Vue Composition API来学习。