Vue3.0会带来什么?—— 2018 vue conf回顾笔记

321 阅读8分钟

这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战

bilibili:BV1Et41197L4

  • 更快
  • 更小
  • 更易于维护
  • 更好的多端渲染支持
  • 新功能

更快

模板编译和Virtual DOM运行时优化

  • 新的Virtual DOM实现
  • 完全重构
  • 初始渲染/更新提速达100%(最高)
  • 更多编译时的优化
  • 以减少运行时的开销

当我们将模板编译为Virtual DOM的渲染函数的时候,Vue2.0的做法是不管是组件还是浏览器自带的原生html元素,都是统一作为一个字符串,然后传到h函数,也就是创建一个虚拟node的函数里面。判断一个元素是组件还是原生元素时,Vue2.0是在运行时,不可避免的就会有运行时开销,Vue3.0这个判断是在编译时完成。这就是所谓的component Fast Path。 还有,要尽可能生成所谓的叫Monomorphic calls,就是在生成这些虚拟node时,我们的函数调用要尽可能的形状一致,也就是它有同样个数的参数,这样会使得我们生成的代码更易于被JS引擎去优化。 最后,在模板中直接静态地分析一个元素所包含的子元素的类型,比如可以在生成的代码里给运行时留下一些类似于hint的数字,提示这个div现在只有一个子元素,算法里对应的分支就可以直接跳入对象的只有一个子元素的这个分支,就可以跳过很多其他不必要的判断。

优化Slots的生成 之前每一次变动要更新父组件,父组件更新同时生存新的slot内容传到子组件,然后子组件也更新。 在新的更新机制里面,我们把所有的slot都跟scope slot一样统一成为一个函数,可以是一个lazy函数。当你把函数传给子组件时,由子组件来决定什么时候调用这个函数。当子组件调用函数时,当组件变动时只需要重新渲染子组件,父组件跟子组件的依赖就分开了。这样可以得到一个非常精确的组件级的依赖收集,可以进一步避免不必要的渲染。任何时候,只有真正依赖了某个数据的组件才可能会重渲染,就不存在需要去手动优化组件过度重绘这个问题。

静态内容提取 Vue2.0已经有做。检测到一部分模板是不会变的,就可以提取出来,在之后的更新中,这一部分模板不仅可以直接复用之前的Virtual DOM,连比对整个树的过程都可以直接跳过。Vue2里没有做的一点是,当一个元素它内部包含任意深度包含任意动态内容的时候,那整个元素都无法被静态化,这就很可惜;但我们其实还是可以做一定程度的优化,那就是如果这个元素本身上面所有的属性都是静态的,我们可以把这个属性对象给它提取出来,提取出来之后,我们每次比对这个元素的时候,如果这个元素的data都是一样,那这个元素本身就不需要再比对了,只需要直接跳过,去比对它的children就可以了。

内联事件函数提取 内联函数每次重新渲染都会生成一个新的函数,这会导致子组件每次都会重新渲染,通过一定程度的优化,就是把内联函数生成一次之后cache下来,之后每一次都重用同一个函数,就可以避免子组件无谓更新的一个效果。

基于Proxy的新数据监听系统

  • 全语言特性支持 + 更好的性能
  • 对象属性增删
  • 数组index/length更改
  • Map、Set、WeakMap、WeakSet
  • Classes

事实上这个基于Proxy的监听,是所谓的Lazy by default,就是只有当一个数据被用到的时候,我们才会监听它。

利用Proxy减少组件实例初始化开销 同时,我们知道每个Vue组件都会代理它所包含的所有的data、coputed以及props,这些代理都是通过Object.defineProperty来实现的,在实际的这个实例生成中,其实大量的Object.defineProperty是一个相对昂贵的操作。在Vue3里面,暴露给用户的this其实是真正的组件实例的一个Proxy,当你在这个Proxy上获取一些属性的时候,我们内部再做判断。这样就彻底避免了Object.defineProperty 的使用。


更小

便于Tree-shaking的代码结构

Tree-shaking就是把我们没有用到的代码在最后编译的时候给它扔掉。 之前Vue的整个代码只有一个Vue对象,所有东西都在Vue对象上,这样的话其实所有你没用到的东西也没办法扔掉,因为它们全都已经被增添到Vue这个全局对象上了。Vue3做成了按需引入,用ES module imports按需引入。举例来说,如以下这些就可以做成按需引入:

  • 内置组件(keep-alive, transition...)
  • 指令的运行时helper(v-model, v-for...)
  • 各种工具函数(asyncComponent, mixins, memoize...)

同时很多这些指令在编译时就可以生成对应的代码。也就是当你使用了v-model的时候,我们生成的代码才会去import v-model相关的代码。这就保证了你只有最基本的功能的时候,你就只会用到最核心的任何Vue都会用到的那部分代码。这一部分代码在Tree-shakin之后大小大概在10KB左右。(新的最小核心运行时:~10kb gzipped)


更易于维护

这一点是对于开发Vue的团队而言。对于想要阅读Vue源码,想要参与到Vue开发当中来的同学也是很有意义的。

Flow-> TypeScript(不影响用户代码) 以后全部改用TypeScript,用户可以选择不用。

内部模块解耦

编译器重构

  • 插件化设计
  • 带位置信息的parser(source maps)
  • 为更好的IDE工具链铺路

更好的多端渲染支持

Vue3会引入一个真正的Custom Render API,开发者需要做的就是从runtime-core里面import这个createRenderer,而不必像之前需要forkVue源码再处理。runtime-core就是一个平台无关的Vue的runtime,它包含了vue的各种组件,Virtual DOM的这些算法等等,但是它不包含任何跟DOM直接相关的代码,跟DOM相关的代码就在你createRenderer的时候,通过这个nodeOps,这些就是节点操作,以及patchData,就是如何处理一个元素在被更新的时候它上面的各种属性的操作。提供这些对于的函数之后,你就获得了一个render函数,这个render函数可以让你把Vue的组件和Virtual DOM直接渲染到你的原生对象上面去。

import { createRenderer } from '@vue/runtime-core'

const { render } = createRenderer({
    nodeOps,
    patchData
})

新功能

响应式数据监听API

用observable函数去显示地创建一个响应式对象。这个就跟你把一个对象传到Vue的组件的data里面,被转化之后它就变成了一个响应式的。

import { observable, effect } from 'vue'

const state = observable({
    count: 0
})

effect(() => {
    console.log(`count is: ${state.count}`)
})

state.count++ // count is 1

暴露这个API的主要目的是可以使用observable实现跨组件的状态共享。

轻松排查组件更新的触发原因

在Vue3里提供了renderTriggered这个API,每一次组件出发了更新的时候,就可以使用debugger,在浏览器里面直接地看到究竟是哪一行触发的这个更新。

const Comp = {
    render(props){
        return h('div', props.count)
    },
    renderTriggered(event){
        debugger
    }
}

更好的TypeSCript支持,包括原生的Class API和TSX

直接内置原生支持

更好的警告信息

  • 组件堆栈包含函数式组件
  • 可以直接在警告信息中查看组件的props
  • 在更多的警告中提供组件堆栈信息

Experimental

Hooks API(参见React)

作为一种逻辑复用机制,大概率取代mixins

Time Slicing Support(参见React)

Time Slicing其实也是React前段时间提出的一个概念,叫做Concurrent React,也就是说能够让我们的这个框架在进行Javascript计算的时候,把这些Javascript计算切成一块块的,一帧一帧地去做。因为作为框架最容易导致的性能问题,就是在浏览器的主线程里面,进行大量的Javascript操作,会使得整个主线程被block。要改进这个问题可以通过每16毫秒,也就是每一帧,我们就把做的这些yield给浏览器,让用户事件重新进来,然后触发更新。这有可能出现用户事件导致的新的更新,使得之前的更新被invalid,也就是可以不用去做。

关于IE

会有一个专门的版本,在IE11中自动降级为旧的getter/setter机制;并对IE中不支持的用户给出警告。