31

Vue3.0响应式系统二三事

 4 years ago
source link: https://quincychen.cn/vue-next-reactivity/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

eaYrQzz.jpg!web

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

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

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

  • 因为现有语法被移除或者被替代,所有 Vue 代码都必须重写
  • 新语法没有强制结构,会导致意大利苗条式代码
  • Vue在模仿 React / Angular,它要变成 React / Angualr 了
  • 所有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')

看起来确实和 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 与旧版本响应式系统上的一些区别,更深入的剖析等后面读完源码再开篇吧~

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK