Vue3.0响应式系统二三事

惊觉近来对于新技术的涉猎和思考总结有些懈怠(自我反省ing),赶紧写篇博客消散一下愧疚感。

10月5号,正值国庆佳节,Vue作者尤雨溪在全球最大的同性交友网站 github 开源了 vue@3.0.0alpha 版代码 ,大家可以去看看小右的提交记录和issue处理速度,不得不感慨,优秀的人总是自律且勤奋的。想想当时还在青海浪,已经几个月没写博客的自己,不禁有些汗颜。

浪仔在青海

扯远了,我们说回Vue3,说到 Vue3 不得不提到6月份左右发生的一点小新闻:

故事的背景是,小右在Vue社区发布的一篇请求意见稿(RFC),用于讨论即将发布在Vue3.0中的函数式组件编写方式,不久后,知名论坛 reddit 和 hacker news 上的一些负面帖子引发大批的开发者涌向 RFC 表达他们的愤怒,甚至不乏一些侮辱性评论。

很多人在不经自行推敲就人云亦云的持以下观点:

  • 因为现有语法被移除或者被替代,所有 Vue 代码都必须重写
  • 新语法没有强制结构,会导致意大利苗条式代码
  • Vue在模仿 React / Angular,它要变成 React / Angular 了
  • 所有HTML都要写在一个超长的字符串里

事实上,在认真阅读 RFC 以及部分源码后(顺便提了两个PR: #441, #453),我认为这些观点都是无稽之谈

以上并不是这篇文章想要说的内容,只是想用这件事,警醒正在参与开源工作、从开源中获益的自己:

  • 不要轻信别人的一面之词,要有自己的思考和判断
  • 不要轻易对不了解的东西发表评价,尤其是负面评价
  • 无礼的批评,不是那些对开源投入巨大精力的工作者所应该承受的

希望以上自我警醒对阅读的你也有帮助

话不多说(已经说很多了),进入正题

试试水

首先,如果是想完全拥抱 Vue3.0 的新特性,那么在写法上和 Vue2.x 确实有很大的区别(Vue3.0会提供尽可能兼容 Vue2.x 写法的版本)。

Vue2.x 的写法我就不赘述了,我们来看看,Vue3.0 的写法究竟差别在哪里:

<!-- 单组件写法template和之前一致 -->
<div id="app"></div>
<script src="../packages/vue/dist/vue.global.js"></script>
<script>
  const { reactive, effect, computed, createApp } = Vue
  const App = {
    template: `
      <div>
        <p>state.count: {{ state.count }}</p>
        <p>computedCount: {{ computedCount }}</p>
        <button @click="add">Plus One</button>
      </div>
    `,
    setup() {
      const state = reactive({
        count: 0
      })
      effect(() => {
        console.log('count change ===> ', state.count)
      })
      function add () {
        state.count++
      }
      return {
        state,
        computedCount: computed(() => state.count * 2),
        add
      }
    }
  }
  createApp().mount(App, '#app')
</script>

看起来确实和 Vue2.x 差别有点大,不见以往常见的 created, data 等,还出现了几个陌生的方法:

  • reactive

    创建响应式变量,参数可以是基本数据类型、引用、对象、数组、集合(Map|Set)

  • effect(fn)

    fn默认会先执行一次(可通过 lazy 关闭),若 fn 中有响应式数据,并且其发生变化时,fn 会再次被触发

  • computed(fn)

    基于 effect 实现,返回一个计算值,和 effect 的区别是,computed 中的 fn 不会立即执行,且 fn 不应该有任何副作用,仅仅是返回一个值,当依赖的响应式数据变化时,并且返回值被引用时,会重新计算返回值

小总结:在 Vue3.0 中,响应式系统被单独抽出来,通过 API 的方式将控制权交给开发者。

实际使用上的改变?

从开源框架或者工具的版本规划来说,每一个大版本的发布,都意味着新功能、新特性的出现,那么 Vue3.0 中,有哪些呢?我们来一起看一看

数组的监听

说到数组监听,相信 Vue2.x 的使用者都不陌生并且满腹牢骚,应该每个人在刚开始使用 Vue2.x 的时候,都有过这样的疑问:

咦,数组里的对象数据明明改变了,为什么我的页面内容没有更新??

再深入一点的同学,会知道 Vue2.x 只实现了对数组 push, pop, shift, unshift, splice, sort, reverse 等原型方法的劫持,通过数组下标的方式改变值是不会触发视图更新的。

Vue2.x 的响应式原理:通过 Object.defineProperty 劫持数据,已经被面试问烂了,相信大部分使用 Vue2.x 的朋友都了解过。有不少人认为,Vue2.x 不支持数组下标监听的原因是 Object.defineProperty 方法不支持数组下标的监听,这实际上是错误的。数组本质上就是个 key-value 的键值对集合,Object.defineProperty 当然可以监听数组下标(key) 的访问和赋值。不支持的原因其实另有玄机:

在 javascript 中 数组太多变了array.length = 0 可以直接清空一个数组;array[1000] = 1 则可以直接将一个空数组长度变为 100 等等。对于对象而言,我们可以很容易的穷举它的变化:

  • 添加一个 key-value
  • 删除一个 key-value
  • 改变一个 key 的 value

而对于数组而言,它的变化很难被枚举,每次变化我们都需要重新对数组递归使用 Object.defineProperty 去劫持 setter 和 getter,这会带来巨大的性能开销,尤其是大数据量渲染时,因此 Vue2.x 最终权衡利弊后,做出一定的妥协,只劫持常用的数组方法。

那 Vue3.0 在数组的监听上有变化吗?有,当然有,没有我也不会废这么多话啦~

Vue3.0 中舍弃了 Object.defineProperty ,改用ES6中的新特性 ProxyReflect 作为响应式系统的核心。大概原理如下:

const array = ['2019', 'working in DJI']
const newArray = new Proxy(array, {
  get: function (target, name, value, receiver) {
    console.log('get value')
    return Reflect.get(target, name)
  },
  set: function (target, name, value, receiver) {
    console.log('set value')
    Reflect.set(target, name, value, receiver)
  }
})

得益于此,Vue3.0 轻松的实现了对数组下标的监听代理,因此我们不需要再使用 Vue.$set 更新数组内元素。

更灵活的响应式

不知道大家有没有注意到,实际开发中,一些页面上的数据在页面的整个生命周期中都是不会变化的,这意味着这部分数据实际上不需要被处理成响应式数据,但是,在 Vue2.x 中我们并没有选择的余地,因为 data () {} 返回的数据都会被处理成响应式。

在 Vue3.0 中,通过暴露 reactive, computed, effect 方法将控制权交给开发者,我们可以自行决定所有模板数据的处理方式。

这样做的好处有:

  • 提高组件初始化速度

    Vue2.x 通过提前对 data 递归使用 Object.defineProperty 构建响应式数据,而Vue3.0 中,这个权利交给了开发者,初始化时不需要再进行这一步,因此提高了组件实例化的速度

  • 降低运行内存

    因为需要监听的数据较 Vue2.x 有所减少,Vue3.0 中需要维护的运行时依赖项 Dep 占用空间会有所降低

Map/Set/WeakMap/WeakSet 的监听

Vue3.0 还支持对 Map/Set/WeakMap/WeakSet 的监听,通过一个普通对象(也拥有 get, set, has, delete, clear 等方法)模拟 Map/Set/WeakMap/WeakSet 类型,解决 Proxy 在代理这些特殊类型对象时的 this 指向问题。感兴趣的可以看看这篇文章:vue3响应式系统源码解析-Reactive篇

兼容性

emmm,由于 Proxy 来自于 ES6,并且目前没有 Polyfill 方案,从Vue官方的消息得知,Vue3.0 只会支持到 IE11,至于更低版本的 IE 则直接被放弃了。不过想想也还好,毕竟连 IE 他爸巨硬都已经放弃这个亲儿子了。

最后

本文只是简单的对比一下 Vue3.0 与旧版本响应式系统上的一些区别,更深入的剖析等后面读完源码再开篇吧~

最后再鞭策一下自己,最近在自我提升上有些懈怠,还是要尽快调整,毕竟,离所追求的人和物还有很长的一段路要走

0%