给对象添加新属性视图不更新?

9/5/2022 setdefinePropertyassignforceUpdate

# 前言

Vue不能监测对象的变化,无法检测property的添加或移除。
给响应式对象添加一个property,如果是普通方式添加(this.myObject.newProperty = 'hi'),Vue是无法监测的,导致无法触发视图的更新。

# 产生原因

Vue2是通过Object.defineProperty实现数据响应式的,把传给data的对象所有属性全部转为getter/setter
后续如果用普通方式给obj新增属性(obj.bar = '新属性'),新增的bar属性并不是响应式的,并没有通过Object.defineProperty设置成响应式。 defineProperty (opens new window)

const obj = {}
// 访问foo属性(get)or设置foo值(set)的时候触发getter和setter函数
Object.defineProperty(
  obj, 
  'foo', 
  {
        get() {
            console.log(`get foo:${val}`);
            return val
        },
        set(newVal) {
            if (newVal !== val) {
                console.log(`set foo:${newVal}`);
                val = newVal
            }
        }
  }
)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 解决方案

Vue不允许在已创建的实例上动态添加新的响应式属性,要实现数据与视图同步更新,三种解决方案:

  • Vue.set()
  • Object.assign()
  • $forceUpdate()

# Vue.set

Vue.set(target, propertyName/index, value)
Parameter:

  • target: Object || Array
  • propertyName/index: string || number
  • value: any

return: 设置的值

function set (target: Array<any> | Object, key: any, val: any): any {
  // 对属性进行响应式处理
  defineReactive(ob.value, key, val)
  // 通知依赖
  ob.dep.notify()
  return val
}
// defineReactive内部借助Object.defineProperty实现属性拦截
function defineReactive(obj, key, val) {
    Object.defineProperty(obj, key, {
        get() {
            console.log(`get ${key}:${val}`);
            return val
        },
        set(newVal) {
            if (newVal !== val) {
                console.log(`set ${key}:${newVal}`);
                val = newVal
            }
        }
    })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# Object.assign

直接使用Object.assign()添加到对象的新属性不会触发更新,应创建一个新的对象,合并原对象和混入对象的属性

// 代替Object.assign(this.someObject, { a: 1, b: 2 }) => 不会触发更新
this.someObject = Object.assign(
  {},
  this.someObject,
  {
    newProperty1:1
  }
)
1
2
3
4
5
6
7
8

# $forceUpdate

如果发现需要在Vue中做一次强制更新,100%是在某个地方写的有Bug。
没有注意数组or对象的变化监测、依赖了一个未被Vue的响应式追踪的状态。

$forceUpdate迫使Vue实例重新渲染
tip:仅仅影响实例本身和插入插槽内容的子组件,并非所有子组件。

# 总结

  • 给对象添加少量的新属性,可以用Vue.set()。
  • 给对象添加大量的新属性,可以用Object.assign()创建新对象
  • 实在不行,$forceUpdate()进行强制刷新。
  • Vue3是用过proxy实现数据响应式的,直接动态添加新属性是可以实现数据响应式。
Last Updated: 9/5/2022, 6:33:02 PM