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

逻辑代码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]获取到的是代理对象),因此能触发响应式。

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

阅读 2.3k
3 个回答

原因跟你理解的一样。在下面例子中执行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);
  }
});

因为在 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

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>
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏