Vue 触发组件更新
作者:Seiya
时间:2019年08月22日
数据来源
Vue 组件的数据来源有三种:
来自父元素的属性;
来自组件自身的状态 data;
来自状态管理器,如:vuex,Vue.observable;
状态 data 和属性 props
那么,状态 data 和属性 props 的区别如下:
状态是组件自身的数据;
属性是来自父组件的数据;
状态的改变未必会触发更新;
属性的改变未必会触发更新;
Vue 响应式系统
非侵入性的响应式系统是 Vue 最独特的特性之一。数据模型仅仅是普通的 JavaScript 对象。而当你修改它们时,视图会进行更新。在上篇文章《Vue 单向数据流和双向绑定》中,我们对 vue 响应式系统的实现进行详细描述。这篇文章着重描述 vue 响应式系统的局限以及注意事项。
检测变化的注意事项
受现代 JavaScript 的限制 ,Vue 无法检测到对象属性的添加或删除。因为 vue 在初始化实例,劫持属性的过程中,只有预先定义在 data 里面,vue 才能将它转化为响应式。如:
var vm = new Vue({
data() {
this.b = 1;
return {
a: 1
}
}
})
vm.a = 2
// `vm.a` 是响应式的
vm.b = 2
// `vm.b` 是非响应式的(因为没有定义在 data 中)
vm.c = 2
// `vm.c` 是非响应式的(因为没有定义在 data 中)
对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。 但是,可以在 data 里的对象上增加属性。比如:使用 Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式属性。
var vm = new Vue({
el:"#app",
data:{
person:{
age:18
}
}
})
Vue.set(vm.person,"name","zhangsan");
也可以这样使用:
mounted: {
this.$set(this.person, "name", "zhangsan");
}
tips
vm.$set 实例方法是全局 Vue.set 方法的别名。
如果需要为已有对象赋值多个新属性,比如使用 Object.assign() 或 _.extend()。但是,这样添加到对象上的新属性不会触发更新。在这种情况下,我们可以将原对象与要混合进去的对象的属性一起创建一个新的对象。 举例如下:
// 代替 Object.assign(this.person, {name: "zhangsan", sex: "man"})
this.person = Object.assign({}, this.person, {name: "zhangsan", sex: "man"})
声明响应式属性
上面也提到了,由于 Vue 不允许动态添加根级响应式属性,所以你必须在初始化实例前声明所有根级响应式属性,哪怕只是一个空值。
这样的限制消除了在依赖项跟踪系统中的一类边界情况,也使 Vue 实例能更好地配合类型检查系统工作。与此同时,提前声明所有的响应式属性,可以让组件代码在未来修改或给其他开发人员阅读时更易于理解。
异步更新队列
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。
然后,在下一个的事件循环“tick”中,Vue 刷新队列并执行实际 (已去重的) 工作。Vue 在内部对异步队列尝试使用原生的 Promise.then、MutationObserver 和 setImmediate,如果执行环境不支持,则会采用 setTimeout(fn, 0) 代替。
为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用 Vue.nextTick(callback)
。这样回调函数将在 DOM 更新完成后被调用。举例如下:
var vm = new Vue({
el: '#example',
data: {
message: '未更新'
}
})
vm.message = '已更新' // 更改数据
vm.$el.textContent === '已更新' // false
Vue.nextTick(function () {
vm.$el.textContent === '已更新' // true
})
因为 $nextTick() 返回一个 Promise 对象,所以你可以使用新的 ES2016 async/await 语法完成相同的事情:
methods: {
updateMessage: async function () {
this.message = '已更新'
console.log(this.$el.textContent) // => '未更新'
await this.$nextTick()
console.log(this.$el.textContent) // => '已更新'
}
}