当前位置: 首页 > 知识库问答 >
问题:

前端 - Vue3 对象内部修改自身属性值后,如何触发响应式?

穆建华
2024-06-13

逻辑代码Demo:

<script setup lang="ts">import { UnwrapNestedRefs, reactive, watch } from "vue";class User {  name: string;  status: string;  constructor(name: string) {    this.name = name;    this.status = "A";    // 执行一些异步逻辑,会修改自身属性值    this.runAsync();  }  runAsync() {    setTimeout(() => {      // 第一处 这里修改后页面不会更新      console.log("1s后修改user自身的属性", this);      this.status = "B";    }, 1000);  }}class Group {  users: UnwrapNestedRefs<User>[];  constructor() {    this.users = [];  }  addUser(user: User) {    this.users.push(user);  }}const group = reactive(new Group());watch(  () => group.users,  (users) => {    console.log("users changed", JSON.stringify(users));  },  {    deep: true,  });const add = () => {  const user = reactive(new User("user1"));  group.addUser(user);};const change = () => {  // 第二处 这里修改后页面正常更新  group.users[group.users.length - 1].status = "AA";};const show = () => {  console.log(JSON.stringify(group.users));};</script><template>  <p>UploaderFiles</p>  <button @click="add">Add</button>  <button @click="change">Change</button>  <button @click="show">show</button>  <div>    <div v-for="(user, index) in group.users" :key="index">      <span>{{ user.name }}</span>      <span> - </span>      <span>{{ user.status }}</span>    </div>  </div></template>

大概的逻辑如上,重点在于User对象创建后会异步执行一些逻辑,然后改变自身的个别属性值,期望是页面上能实时更新改变后的数据。

大概原因我也了解,因为第一处修改的原始对象自身,其自身不具备响应式,而第二处修改的是创建的代理对象(group.users[group.users.length - 1]获取到的是代理对象),因此能触发响应式。

想寻求一个解决方案,如果确实需要在对象内部修改其属性值,怎么才能触发响应式?

共有3个答案

子车高超
2024-06-13

https://github.com/vuejs/core/issues/3743#issuecomment-835802036

https://stackoverflow.com/questions/43431550

<script setup lang="ts">import { ref } from "vue";class User {  status: string;  constructor() {    this.status = "A";    return (async () => {      this.status = "B";      // Constructors return `this` implicitly, but this is an IIFE, so      // return `this` explicitly (else we'd return an empty object).      return this;    })();  }}const user = ref();(async function () {  user.value = await new User();})();</script><template>  <span v-if="user">status: {{ user.status }}</span></template>
江宏深
2024-06-13

因为在 constructor 中,你的 runAsync 函数执行时 this 指向的是被创建的源 User 实例,而不是使用 reactive 创建的被 Proxy 代理的 User 实例。

把你的 runAsync() 执行放到 User 实例化之后去执行,而不是在 constructor 中直接执行。

const add = () => {  const user = reactive(new User("user1"));  user.runAsync()  group.addUser(user);};

你可以对比两次执行 runAsync 函数时,控制台输出的 this 查看到两次执行的 this 不同指出。


深入响应式系统 | Vue.js
javascript - Vue 3 reactivity not triggered from inside a class instance - Stack Overflow

冉弘化
2024-06-13

原因跟你理解的一样。在下面例子中执行target.add()和执行proxy.add()效果是不一样的,因为target不是代理数据(类比你的new User),运行target.add(),里面的this指向target,是在对一个正常对象进行操作(你的this.runAsync()也是这个原理)。但是执行proxy.add()就不一样,里面的this指向proxy,是对代理数据进行操作,回到vue中就是可以出发响应式。所以可以把this.runAsync()挪出来,通过user.runAsync()执行。或者看有啥办法可以在constructor中把this变成响应式数据。

var target = {    a: 1,    add: function(){        this.a++        console.log(this.a)    }}var proxy = new Proxy(target, {  get: function (target, propKey, receiver) {    console.log(`getting ${propKey}!`);    return Reflect.get(target, propKey, receiver);  },  set: function (target, propKey, value, receiver) {    console.log(`setting ${propKey}!`);    return Reflect.set(target, propKey, value, receiver);  }});
 类似资料: