Vue3响应式系统对组件更新的改进

337 阅读1分钟

vue3采用Proxy重写了响应式系统,用来替换vue2 中的DefineProperty方式,Proxy 相对 DefineProperty 做了哪些改进

除了老生常谈的这些

  • 不需要初始化时进行深度遍历
  • 可以监听动态属性的添加或删除
  • 可以监听到数组的索引和数组length属性变化

分析对组件更新的影响

因为 DefineProperty 对增删,数组没有或不能依赖收集,为了保证可用($set, push等),vue把依赖收集全部放到了一个变量中,任何一项改动都会引起组件的更新

数组

vue2没有对数组中的每一项做响应式处理,所有的响应都是通过 数组 本身这个对象进行收集

export default {
    data() {
        return {
          arr: [1]
        }
    }
}

此时arr经过处理为:

image.png

源码

export function observe(value){
  if (isArray(value)) {
    return new Observer(value)
  }
}
export class Observer {
  constructor( value) {
    this.dep =  new Dep()
    this.vmCount = 0
    def(value, '__ob__', this)
    if (isArray(value)) {
      ;(value).__proto__ = arrayMethods
      for (let i = 0, l = value.length; i < l; i++) {
         observe(value[i])
      }
    }
  }
}
const methodsToPatch = ['pop','shift','unshift', ...]

methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args)
    const ob = this.__ob__
    //  ... 省略
    ob.dep.notify() // 触发更新
    return result
  })
})

可以看到所有的响应式收集和触发都是通过__ob__这个属性来操作。这里会造成不必要的更新,任何对数组arr的改动都会影响使用arr的组件(即使只使用了arr[0], arr.push(1)也会造成组件重新渲染)

Vue3可以收集数组索引和length属性变化,可以精确到自己使用的属性,不会有这个问题

// 假设子组件
export default {
  props: ['arr']
}
<template>
  <div>
    <div>{{ arr[0] }}</div>
  </div>
</template>

此时操作 arr.push(1) Vue3不会更新,Vue2组件会更新

对象

Vue2中数据的增删同样是通过队形本身的__ob__进行收集的,像 has, for...in 依赖整个对象的变化同样会造成一些不必要的更新

// obj: { a: 1}
<script>
export default {
  props: ['obj'],
}
</script>
<template>
  <div>
    <div>{{ obj.a }}</div>
  </div>
</template>

此时操作给obj增加属性 Vue3不会更新,Vue2组件会更新

// 增加属性
this.$set(this.obj, 'c', 1) // vue2
this.obj.c = 1 // vue3