Vue.js2使用Object.assign克隆后watch监听失效问题求助
咱们先搞清楚为啥用Object.assign克隆后watch就失效了:
computed属性的“一次性”克隆问题
你把userFormData写成computed属性,每次访问它都会执行Object.assign({}, this.user)生成一个全新的对象。而且这个computed的依赖只有this.user的引用——只有当this.user本身被替换成新对象时,computed才会重新计算。v-model修改的是“临时对象”
当你用v-model绑定userFormData.fname时,你其实是在修改computed getter返回的那个临时克隆对象的属性。但因为这个computed没有setter,这些修改不会被持久化:下次Vue更新视图或者你再次访问userFormData时,又会从this.user克隆出一个新对象,之前的修改直接就丢了。watch监听不到无效变化
你的watch监听的是computed属性userFormData,但由于computed只有在this.user的引用变化时才会更新,而你修改的是临时克隆对象的属性,根本不会触发computed的重新计算。watch盯着的是computed上次返回的旧对象,自然捕捉不到你修改临时对象的操作。
而之前的代码能正常工作,是因为你直接返回了this.user的引用——v-model修改的是this.user本身的属性,watch的deep: true能捕捉到对象内部属性的变化,所以editsPending会被正确设置。
方案一:把表单数据移到data中(最直接)
把userFormData从computed改成data属性,在组件初始化时克隆this.user,这样它是一个固定的响应式对象,watch可以正常监听深层变化:
<template> <!-- 模板部分保持不变 --> </template> <script> export default { name: 'userProfile', data () { return { editsPending: false, userFormData: {} // 初始化空对象 } }, created() { // 组件创建时克隆原始user数据 this.userFormData = Object.assign({}, this.user); }, watch: { userFormData: { deep: true, handler (val) { this.editsPending = true; } } }, methods: { userFormCancel () { // 取消时重新克隆原始数据,重置表单 this.userFormData = Object.assign({}, this.user); this.editsPending = false; } } } </script>
方案二:给computed添加setter(适合需要保持computed的场景)
如果必须用computed,给它添加setter,内部维护一个本地的响应式副本:
<template> <!-- 模板部分保持不变 --> </template> <script> export default { name: 'userProfile', data () { return { editsPending: false, _userFormData: {} // 内部维护的表单副本 } }, created() { // 初始化克隆原始数据 this._userFormData = Object.assign({}, this.user); }, computed: { userFormData: { get() { return this._userFormData; }, set(newVal) { // 当v-model修改时,更新内部副本 this._userFormData = newVal; } } }, watch: { // 监听内部副本的深层变化 _userFormData: { deep: true, handler (val) { this.editsPending = true; } } }, methods: { userFormCancel () { this._userFormData = Object.assign({}, this.user); this.editsPending = false; } } } </script>
额外说明
如果你的this.user是嵌套对象(比如有深层属性),Object.assign只能做浅克隆。如果需要深克隆,可以用JSON.parse(JSON.stringify(this.user))(注意不支持函数、Symbol等特殊类型),或者如果是复杂对象可以考虑用lodash的_.cloneDeep工具函数。
内容的提问来源于stack exchange,提问作者user2220551

