以下都基于数据已经渲染到 dom 上后再对数据进行修改,console 出来的数据更新了,但绑定的 dom 不更新的问题
- 更新对象的属性不render
data() {
return {
detail: {}
}
}
created() {
this.detail = {
a: ‘1’, // 更新
b: ‘2’ // 更新
}
}
mounted () {
this.detail.c = ‘12’ // 不更新
}
vue 不允许动态添加对象的根级别属性。
Vue 会在初始化实例时对对象的属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的
解决方法:使用 set给对象添加属性或在初始化对象时就声明属性//方法一this.set 给对象添加属性或在初始化对象时就声明属性 // 方法一 this.set给对象添加属性或在初始化对象时就声明属性//方法一this.set(this.detail, ‘c’, 12)
// 方法二
this.detail = {
a: ‘1’,
b: ‘2’,
c: ‘12’
}
2. 更新数组数据不render?
// 场景1
export default {
data () {
return {
detail: []
}
},
created () {
this.detail[0] = { a: 2 }
},
mounted () {
this.detail[0].a = 4 // 不更新
}
}
// 场景2
export default {
data () {
return {
detail: []
}
},
created () {
this.detail[0] = 2
},
mounted () {
this.detail[0] = 3 // 不更新
}
}
数组的索引是没有响应式的,比如上面的 detail 的 0 这个位置是没有 setter/getter 的,所以无法检测到该数据的变更,{ a: 2 } 直接赋值给了 0 的位置,所以也无法对 a 做响应式转化
解决方法: 同样可以使用 $set 给数组的索引的内容执行 getter/setter 转化,但也可以使用 变异方法
使用 $set 设置数组指定位置的数据
this.$set(this.detail, 0, { a: 2 })
变异方法:
push()
pop()
splice()
shift()
unshift()
sort()
reverse()
export default {
data () {
return {
detail: []
}
},
created () {
this.detail.splice(0, 1, { a: 2 })
},
mounted () {
this.detail[0].a = 4 // 更新
}
}
还有一种方式,就是给数组重新赋值
this.detail = [{a: 2}]
源码里,其实在 $set 处理数组时,内部也是通过 splice() 对数组的元素进行操作的
export function set (target: Array | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== ‘production’ &&
(isUndef(target) || isPrimitive(target))
) {
warn(Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}
)
}
// 传入的 第一个参是数组时并且第二个参是有效的索引值
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).ob
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== ‘production’ && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ’ +
‘at runtime - declare it upfront in the data option.’
)
return val
}
if (!ob) {
target[key] = val
return val
}
// 响应式转化: 给对象属性执行 setter/getter 转化
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}
3. 为什么变异方法就能让新增的数据有响应式呢?
这个官方文档并没有细说,但是翻过源码的人就能知道,为什么上面这些数组的原生方法被叫做变异方法呢,字面上了解就是 vue 对原生的这些方法做了一点点的修饰。
// /core/observer/array.js
const methodsToPatch = [
‘push’,
‘pop’,
‘shift’,
‘unshift’,
‘splice’,
‘sort’,
‘reverse’
]
// 通过def 重新定义了数组的原生函数如push等
def(arrayMethods, method, function mutator (…args) {
const result = original.apply(this, args)
const ob = this.ob
let inserted
switch (method) {
case ‘push’:
case ‘unshift’:
inserted = args
break
case ‘splice’:
inserted = args.slice(2)
break
}
// 将数组上述的原生函数的传入的参数做处理
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
observeArray (items: Array) {
for (let i = 0, l = items.length; i // 遍历参数配置响应式
observe(items[i])
}
}