深入Vue响应式
1.什么是Vue的响应式
把一个普通的JS对象,传入到Vue实例作为数据模型data选项时,当你修改它们的时候,视图会重新render。也就是说,当你修改data里面的数据的时候,视图也会变化。
//<div>{{message}}</div>
const vm = new Vue({
data() {
return {
message: '初始得到渲染'
}
}
}).$mount('#app')
当修改为vm.message = ‘修改之后的渲染’,div中的值也会发生改变。
2.实现基本原理
基本实现是通过JS的Object.definePropety
的语法来实现的。当JS对象传入到Vue实例中的时候,Vue将遍历此对象的所有数据propety,并且通过Object.definePropety
内部转化为getter和setter。
通俗点说就是,Vue通过封装,监听和代理实现 数据的变化,从而实现数据重新render,即数据的实时响应。这一系列操作是Vue内部已经处理过了。
简单的封装代理监听如下所示:
// 1.创建一个代理,处理传入的对象
function proxy2({ data }) {
let tempValue = data.n // 用来临时存储对象中的数值
//2. 添加一个监听,来监听初始对象中的值
Object.defineProperty(data, 'n', {
get() {
// 获取传入的对象的值
return tempValue
},
set(newValue) {
// 修改传入对象的值
if (newValue < 0) return
tempValue = newValue
}
})
// 以上的操作就是监听,处理你想要的数据
let obj = {}
Object.defineProperty(obj, 'n', {
get() {
// return tempValue
return data.n
},
set() {
return tempValue
}
})
return obj
}
let myData2 = { n: 0 }
let obj5 = proxy2({ data: myData2 })
console.log(`需求五:${obj5.n}`)
myData2.n = -1
console.log(`需求五:${obj5.n} 设置-1失败了`)
myData2.n = 1
console.log(`需求五:${obj5.n} 设置1成功了`)
3.注意事项
由于JavaScript的限制,Vue不能监听数组和对象的响应。但是有一些方法可以来避免这个问题,让对象和数组也做到实时响应。
3.1 对于对象
对于对象,Vue是不能检测propety的添加和删除。由于在实例初始化的时候,已经对propety执行了getter和setter的转化,所以只有原型在data对象已经声明了的时候,才可以做到实时响应。
new Vue({
data() {
return {
obj: {
a: 1
}
}
},
template: `
<div>
<p>{{obj}}</p>
<p>{{obj.a}}</p>
<p>{{obj.b}}</p>
<p>{{obj.c}}</p>
<button @click="setObj">设置</button>
</div>
`,
methods: {
setObj() {
// this.obj.b = 1 //页面上不会显示出b的值
// this.$set(this.obj, 'b', 2) // 页面上会显示出b的值
// Vue.set(this.obj, 'b', 2) // 页面上会显示出b的值
// 向对象中添加多个属性
this.obj = Object.assign({}, this.obj, { b: 2, C: 3 }) // 页面上会显示出b,c的值
console.log(`设置以后的对象是:${JSON.stringify(this.obj)}`)
}
}
}).$mount('#app')
使用this.obj.b = 1
,这种方法添加的属性页面响应不了,如果对象的属性没有提前声明,要做到响应,则可以使用以下两种方法
Vue.set(this.obj,'xxx',value)
this.$set(this.obj,'xxx',value) // 此方法是全局方法Vue.set()的一个别名
//第一个参数是你要进行添加的对象
//第二个参数是你要添加的属性
//第三个属性是value
还可以向改对象中添加多个属性
this.obj = Object.assign({}, this.obj, { b: 2, C: 3 }) // 页面上会显示出b,c的值
3.2 对于数组
例如:
1.通过数组的所以直接向已有的数组中添加元素:this.tempArr[index] = value
2.直接修改数组的长度:this.tempArr.length = newLength
以上的两种操作是不能响应。
new Vue({
data() {
return {
tempArr: [1, 2, 3]
}
},
template: `
<div>
<div v-for="item in tempArr">{{item}}</div>
<button @click="setArr">设置数组</button>
</div>
`,
methods: {
setArr() {
// this.tempArr[3] = 4 //页面上不会显示
// this.$set(this.tempArr, 3, 4) //页面上会显示
// this.tempArr.splice(0, 1) //页面上会显示
console.log(`改变之后的数组是:${this.tempArr}`)
}
}
}).$mount('#app')
对于第一种情况,也可以使用以下两种方法像数组中添加元素。
this.$set(this.tempArr, 3, 4)
Vue.set(this.rempArr,3,4)
此外,数组还有7个API,分别是
push() // 向数组中添加一个元素
pop() // 删除数组末尾的元素,数组长度减一,并且返回删除的元素
shift() // 删除数组的第一个元素,并返回删除的元素
unshift() // 向数组的开头添加一个或者多个元素,并返回新的数组长度
splice() // 向数组中添加或者删除元素,添加元素的时候,第二个参数为0,第一个参数是起始的下标
sort() // 对数组进行排序
reverse() // 颠倒数组中的元素顺序
这7个API可以做到实时响应。
4.声明响应式propety
由于 Vue 不允许动态添加根级响应式 property,所以你必须在初始化实例前声明所有根级响应式 property,哪怕只是一个空值:
var vm = new Vue({
data: {
// 声明 message 为一个空值字符串
message: ''
},
template: '<div>{{ message }}</div>'
})
之后设置 message
,vm.message = 'Hello!'如果你未在 data 选项中声明 message,Vue 将警告你渲染函数正在试图访问不存在的 property。