未从类实例内部触发 Vue 3 反应性

Posted

技术标签:

【中文标题】未从类实例内部触发 Vue 3 反应性【英文标题】:Vue 3 reactivity not triggered from inside a class instance 【发布时间】:2021-08-25 21:18:12 【问题描述】:

Codepen:https://codepen.io/codingkiwi_/pen/XWMBRpW

假设你有一堂课:

class MyClass 
  constructor()
    this.entries = ["a"];

    //=== example change triggered from INSIDE the class ===
    setTimeout(() => 
      this.entries.push("c");
    , 1000);
  

在一个组件中你会得到该类的一个实例:

const  reactive  = Vue;

const App = 
  setup() 
    const myobject = reactive(new MyClass());

    //=== example change triggered from OUTSIDE the class ===
    setTimeout(() => 
      myobject.entries.push("b");
    , 500);
    
    return 
      myobject
    ;
  

DOM 中的 myobject.entries 数组将显示条目 "a""b",但不显示 "c"

【问题讨论】:

【参考方案1】:

正如另一个答案所解释的,reactive 创建代理对象以启用反应性。构造函数中的this 指的是原始MyClass 实例而不是代理,因此它不能是反应式的。

这表示代码中的探针。 reactive 考虑了MyClass 构造函数中发生的同步操作。在构造函数中执行异步副作用是一种反模式,原因包括可能对使用此类构造函数的代码产生影响。

这可以通过以下方式解决:

class MyClass 
  constructor()
    // synchronous stuff only
    this.entries = ["a"];
  

  init() 
    // asynchronous stuff, probably return a promise
    setTimeout(() => 
      this.entries.push("c");
    , 1000);
  

const myobject = reactive(new MyClass());
myobject.init() // `this` is a proxy inside `init`

【讨论】:

谢谢!构造函数中的反模式实际上只是在示例中:) 在实际代码中,我遇到了这个问题,我在构造函数this.listener = () => this.entities.push(); 中定义了一个侦听器函数(以便以后能够取消订阅),所以,就像你解释的那样,“这个" 在监听函数中引用原始实例 在这种情况下,解决方法是不使用箭头方法和惰性绑定方法,例如listener 是常规方法,用作event => this.listener(event)this.listener.bind(this)。 IIRC 这是github.com/NoHomey/bind-decorator 装饰器可以做的事情,以防您的设置支持它们。正如另一篇文章所建议的那样,该类不一定要在 Vue 组件中使用硬编码的 reactive,但需要在设计时考虑到这一点。【参考方案2】:

DOM 中的 myobject.entries 数组将显示条目 "a""b",但不显示 "c"

这是因为 "a" 已经在数组中,因为我们使实例具有响应性,并且 "b" 的推送是从对象外部通过代理发生的。

明确const myobject 不包含MyClass 实例,它包含处理/包装原始实例的实例的Proxy!它是传递给 DOM / 模板的代理。

"c" 的推送发生在对象内部,不是通过代理。所以"c"会被推送到数组中,但是不会触发reactivity变化。

修复:

将我们从类内部更改的数组显式标记为reactive,如下所示:

class MyClass 
  constructor()
    this.entries = reactive(["a"]);

    //=== example change triggered from INSIDE the class ===
    setTimeout(() => 
      this.entries.push("c");
    , 1000);
  

或者尝试只使用文档建议的代理对象:

这里的最佳做法是永远不要持有对原始原始对象的引用,而只使用响应式版本:

文档:https://v3.vuejs.org/guide/reactivity.html#proxy-vs-original-identity

【讨论】:

以上是关于未从类实例内部触发 Vue 3 反应性的主要内容,如果未能解决你的问题,请参考以下文章

Vue.js 3 - 替换/更新反应性对象而不会失去反应性

如何更新反应性对象(和一般属性)?

Vue 是不是支持 Map 和 Set 数据类型的反应性?

Svelte:双向绑定触发反应性两次

如何使 Vue 3 对象属性具有反应性

在 vue 3 组合 API 中删除反应性对象上的代理