26. Reactivity in Depth(反应性的深度)
深度反应性
Vue最明显的特征之一是不显眼的反应系统。模型只是普通的JavaScript对象。当你修改它们时,视图更新。它使状态管理变得简单直观,但了解它的工作原理以避免一些常见问题也很重要。在本节中,我们将深入挖掘Vue反应系统的一些较低层次的细节。
如何跟踪变更
当您将纯JavaScript对象作为data
选项传递给Vue实例时,Vue将遍历其所有属性并使用Object.defineProperty将它们转换为getter / setter 。这是一个仅限ES5和不可光滑的功能,这就是Vue不支持IE8及更低版本的原因。
getter / setter对用户来说是不可见的,但是它们使得Vue能够在访问或修改属性时执行依赖关系跟踪和更改通知。需要注意的是,当转换的数据对象被记录时,浏览器控制台对getter / setter的格式会有所不同,因此您可能需要安装vue-devtools以获得更易于查看的界面。
每个组件实例都有一个相应的观察器
实例,它将组件呈现期间“触摸”的任何属性记录为依赖关系。稍后当一个依赖关系的setter被触发时,它会通知观察者,这又导致组件重新呈现。
更改检测警告
由于现代JavaScript的限制(以及放弃Object.observe
),Vue 无法检测属性添加或删除
。由于Vue在实例初始化期间执行getter / setter转换过程,所以data
对象中必须存在一个属性,以便Vue将其转换并使其处于被动状态。例如:
var vm = new Vue{
data: {
a: 1
}
})
// `vm.a` is now reactive
vm.b = 2
// `vm.b` is NOT reactive
Vue不允许向已创建的实例动态添加新的根级别反应属性。但是,可以使用以下Vue.set(object, key, value)
方法将反应性属性添加到嵌套对象:
Vue.set(vm.someObject, 'b', 2)
您也可以使用vm.$set
实例方法,它是全局Vue.set
的别名:
this.$set(this.someObject, 'b', 2)
有时您可能想要为现有对象分配多个属性,例如使用Object.assign()
or _.extend()
。但是,添加到对象的新属性不会触发更改。在这种情况下,使用原始对象和mixin对象的属性创建一个新对象:
// instead of `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign{}, this.someObject, { a: 1, b: 2 })
还有一些与数组相关的警告,在前面列表呈现部分讨论过。
声明反应性属性
由于Vue不允许动态添加根级别反应属性,因此即使使用空值,也必须先声明所有根级反应数据属性来初始化Vue实例:
var vm = new Vue{
data: {
// declare message with an empty value
message: ''
},
template: '<div>{{ message }}</div>'
})
// set `message` later
vm.message = 'Hello!'
如果你没有message
在数据选项中声明,Vue会警告你渲染函数试图访问一个不存在的属性。
这个限制背后有技术原因 - 它消除了依赖关系跟踪系统中的一类边缘情况,并且还使Vue实例在类型检查系统中更好地发挥作用。但是在代码可维护性方面也有一个重要的考虑因素:data
对象就像组件状态的模式。预先声明所有反应性属性使组件代码在稍后重新访问或由其他开发人员阅读时更易于理解。
异步更新队列
如果你还没有注意到,Vue 异步
执行DOM更新。无论何时观察到数据变化,它都会打开一个队列并缓冲在同一个事件循环中发生的所有数据变化。如果多次触发同一个观察者,它将仅被推入队列一次。这种缓存的重复数据删除对于避免不必要的计算和DOM操作非常重要。然后,在下一个事件循环“tick”中,Vue刷新队列并执行实际的(已经失效的)工作。内部Vue尝试本地Promise.then
和MutationObserver
异步
排队并返回到setTimeout(fn, 0)
。
例如,设置时vm.someData = 'new value'
,组件不会立即重新渲染。当队列被刷新时,它会在下一个“打勾”中更新。大多数情况下,我们不需要关心这一点,但是当您想要执行取决于更新后DOM状态的事情时,这可能会非常棘手。尽管Vue.js通常鼓励开发人员以“数据驱动”的方式思考,并避免直接触摸DOM,但有时可能需要弄脏手。为了等到Vue.js完成数据更改后更新DOM,可以Vue.nextTick(callback)
在数据更改后立即使用。回调将在DOM更新后调用。例如:
<div id="example">{{ message }}</div>
var vm = new Vue{
el: '#example',
data: {
message: '123'
}
})
vm.message = 'new message' // change data
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
vm.$el.textContent === 'new message' // true
})
还有一个vm.$nextTick()
实例方法,在组件中特别方便,因为它不需要全局Vue
,它的回调的this
上下文将自动绑定到当前的Vue
实例:
Vue.component('example', {
template: '<span>{{ message }}</span>',
data: function () {
return {
message: 'not updated'
}
},
methods: {
updateMessage: function () {
this.message = 'updated'
console.log(this.$el.textContent) // => 'not updated'
this.$nextTick(function () {
console.log(this.$el.textContent) // => 'updated'
})
}
}
})