Vue响应式及其缺陷
我们喜欢Vue的原因之之就是其响应式系统。如果改变一个数据值,它会触发一个页面的更新来反映这个变化。
例如:
let app = new Vue({
el: '#app',
data () {
return {
message: 'Hello World!'
}
}
})
setTimeout(() => {
app.message = 'Goodbye World!'
}, 2000)
<div id="#app">
<h1>{{ message }}</h1>
</div>
比如这个示例,数据data
的message
是响应式的,这也意味着,如果它被更改,将会触发页面的重新渲染。
页面一开始渲染出来的是Hello World
。然后2000ms
之后,message
的值换成了Goodby World
。由于message
的值改更了,所以页面{{ message }}
会重新渲染。看到的效果就如上图所示。
自动响应式配置中的陷阱
当你创建数据属性,计算属性和绑定属性等,Vue会自动配置响应式:
- 节约了我们的时间
- 使我们的代码更简洁
- 有助于减少我们的认知
简言之就是让事情变得简单。这种简单却又会让我们掉到陷阱中,就像自动汽车一样,自动响应让我们变得懒惰,当它不工作的时候,我们并不知道是为什么?
好的响应式变化的时候
UItimate Vue.js Developers系列教程中的一位学生提了一个很有趣的问题。他正在研究Vue海报店,要求你使用Vue制作一个购物车。
商店里展示的产品最初是这样表示的:
var myProduct = {
id: 1,
name: 'My Product',
price: 9.99
}
但是,当你在购物车中添加产品时,需要一个数量,他是这样处理的:
function addToCart (id) {
var item = this.cart.findById(id);
if (item) {
item.qty++
} else {
item.qty = 1;
this.cart.push(item)
}
}
addToCart(myProduct.id)
该方法的逻辑如下:
- 在购物车中找到商品
- 如果它在那里,那么增加数量
- 如果它不在那里,给它一个数量
1
,然后把它加到购物车里
问题出现了
购物车模板仅显示购物车项目列表:
<ul class="shopping-cart">
<li v-for="item in cart"> {{ item.name }} x {{ item.qty }} </li>
</ul>
问题是,无论qty
的值是什么,模板总是显示其值为1
。
我最初以为addToCart
函数的逻辑有问题。但调试后我才发现qty
属性的值增加时都会调用该方法,这不是问题。
响应式如何覆盖
在大多数情况下,Vue的响应式系统会让人觉得方便,但当它不像你想象的那样工作时,会引起混乱和让人沮丧。
如果你理解它是如何工作的,这在很大程度上是可以避免上面提到的现象。
getter
和setter
JavaScript对象的默认行为是直接访问或修改其属性,比如:
var myObj = {
a: 'Hello World'
}
console.log(myObj.a) // => Hello World
但是,使用getter
和setter
这些方法将覆盖对象的默认行为。如果你不知道我在说什么,那建议你阅读一些关于getter
和setter
的教程。
当创建一个Vue实例时,每个数据属性、组件属性等都是可以遍历的,并为每个数据属性添加了getter
和setter
。getter
和setter
允许Vue观察数据的更改并触发更新。
reactiveSetter()
回到我们的product
对象,我们可以这样定义它:
{
id: 1,
name: 'My Item',
price: 9.99
}
Vue实例化后,我们可以在控制台中查看该对象,并查看Vue在其上定义的getter
和setter
:
getter
和setter
函数可以做很多事(查看源码),但是rectiveSetter
的任务之一就是触发一个更改通知,它会导致页面重新渲染。
警告
这是一个很棒的系统,尽管是一个错误系统。如果你在Vue实例化后添加(或删除)一个属性(例如在方法或生命周期钩子中),Vue是不知道它的。
// In the addToCart method, called after instantiation
myProduct.qty = 1;
看,虽然qty
是在对象上定义的,但它没有getter
和setter
:
更新响应式对象
在购物车示例中,解决问题的方法是在添加到购物车时创建一个新对象,而不是添加属性。这是为了确保Vue有机会定义响应式的getter
和setter
:
function addToCart (id) {
var item = this.cart.findById(id);
if (item) {
item.qty++
} else {
// 不要添加一个属性,比如 item.qty = 1
// 创建一个新的对象来替代它
var newItem = {
id: item.id,
name: item.name,
price: item.price,
qty: 1
}
this.cart.push(item)
}
}
addToCart(myProduct.id)
Vue.set
但是,如果你不想创建一个新的对象,你可以使用Vue.set
设置一个新的对象属性。该方法确保将属性创建为一个响应式属性,并触发视图更新:
function addToCart (id) {
var item = this.cart.findById(id);
if (item) {
item.qty++
} else {
// 不要直接添加一个属性,比如 item.qty = 1
// 使用Vue.set 创建一个响应式属性
Vue.set(item, 'qty', 1)
this.cart.push(item)
}
}
addToCart(myProduct.id)
数组
像对象一样,数组也是响应式的,并观察到其变化。与对象一样,数组也有用于操作的警告。Vue也可以封装数组方法,比如push
、splice
等,它们也会触发视图的更新。
当直接使用索引(index
)设置数组项时,这是不太可能的:
app.myArray[index] = newVal;
同样可以使用Vue.set
来设置数组项:
Vue.set(app.myArray, index, newVal);
本文根据@ANTHONYGORE 的《Reactivity In Vue.js (And Its Pitfalls)》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:https://vuejsdevelopers.com/2017/03/05/vue-js-reactivity/。
如需转载,烦请注明出处:https://www.fedev.cn/vue/vue-reactivity-and-pitfalls.htmlnike air max 2019 size