vue 无法监听实例内部修改的变化

2022-04-07 22:02:39 +08:00
 zhaojingfeng
const form = reactive(new T_RAGENCY())
export class T_RAGENCY {
 ID:number
 updateID = () => {
	this.ID++
 }
}
form.updateID()

watchEffect 或 watch 无法监听到变化. 页面中双向绑定的值也无法变化.

1845 次点击
所在节点    程序员
15 条回复
zhaojingfeng
2022-04-07 22:13:05 +08:00
有大佬知道怎么解决么
noe132
2022-04-07 22:19:08 +08:00
你试试在 updateID 把 "this" log 出来看看是什么?
ymcz852
2022-04-07 22:21:17 +08:00
我猜测是 reactive 返回了一个 T_RAGENCY 实例的代理 proxy 对象(响应式副本),实际上监听的是 proxy 对象 form.updateID() 更新的是原始实例的值,proxy 对象监听不到
Kiza
2022-04-07 22:25:45 +08:00
我一般不这么用,我找了一个帖子,你看看是否有帮助。https://stackoverflow.com/questions/69050412/vue-composition-api-and-reactive-class
noe132
2022-04-07 22:28:24 +08:00
@ymcz852 你仔细看看代码,updateID 的 this 到底是什么
Kawa
2022-04-07 22:36:19 +08:00
不知道你有没有了解过 Proxy 和 Vue toRaw 的原理.
简单的用例大概是这样的:
const ProxyObj = Proxy(obj, {
get(target, property) {
console.log("read: "property);
return target[property];
},
set(target, property, value) {
console.log("write: "property);
target[property] = value;
}
});
Vue 的 reactive 基本实现原理就是 Proxy.
想象上面 Proxy 的用例, 如果直接对 obj 写入, 那显然不会触发 Proxy 里的 set handler.
如果对 ProxyObj 写入, 那么就会触发 set handler.
同样的道理, 如果你想触发 reactive 的更新, 那么你就需要对 reactive 包裹过的对象执行写入, 而不是对原对象.

而且我认为 reactive 应该仅存数据, 而不应该包括方法.
ymcz852
2022-04-07 22:39:31 +08:00
Kawa
2022-04-07 22:41:00 +08:00
当然, 如果你非要这样做, 也是有办法的.
比较简单的方法就是做一个 factory, 通过 factory 创建裸对象, 再将其用 reactive 包裹, 最后向其注入 mutation 方法.
非要 new 也不是不行, 可以在类的内部自行维护一个 reactive 对象, 然后定义类属性的 getter 和 setter, 将操作映射到 reactive 对象上.
nomagick
2022-04-07 22:48:33 +08:00
assert(this instanceof T_RAGENCY)

括号函数 this 是在构造的时候决定的,但 vue 拿到你这个对象之后是把上面的属性和方法拿走,舍弃了最初的实例。
之前 vue 就有这个问题,没有维护原型链
noe132
2022-04-07 22:58:32 +08:00
@ymcz852 用了这么久 JS ,我居然把这个搞错了~

这个问题我终于搞明白了。因为 class field 用的是 [[Define]] ( https://github.com/tc39/proposal-class-fields)
class { updateId = () => console.log(this) }
相当于
class {
constructor(){
Object.defineProperty(this, 'updateId', { value: () => console.log(this), enumerable: true, configurable: true, writable: true });
}
}

此时这个方法相当于是在构造函数内定义的,箭头函数内的 this 绑定成了构造函数执行时的 this 。
当 class instance 被 proxy 包了一层后,调用 updateId 拿到的 this 是原对象而不是 proxy ,导致更新没法被检测到。

如果把方法定义成 class method ,this 就是 proxy 。
zhaojingfeng
2022-04-08 08:52:17 +08:00
感谢🙏@Kawa

最后解决方案可行

export class T_RAGENCY {
ID:number
updateID = () => {
this._.ID.value++
}
_:UnwrapNestedRefs<any>
}

form._ = {...toRefs(form)}
Curtion
2022-04-08 10:09:55 +08:00
10 楼说得很清楚了,更方便的是更改 updateID 方法的定义方式:
export class T_RAGENCY {
ID:number
updateID() {
this.ID++
}
}
Kawa
2022-04-08 11:14:39 +08:00
@Curtion
你这么说我才发现他用的方法是用属性的形式去定义的…
这种写法正常写还真写不出来吧
daolanfler
2022-04-08 13:50:59 +08:00
dreamerblue
2022-04-08 22:39:26 +08:00
一看看到箭头函数就猜到会出问题...不知道是不是从 React 类组件带过来的习惯?写 Vue 就用最符合直觉的方式去定义方法就好了,无论是写选项式组件还是类组件 /服务都是一样的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/845573

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX