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

Posted

技术标签:

【中文标题】如何更新反应性对象(和一般属性)?【英文标题】:How to update reactive Objects (and properties in general)? 【发布时间】:2017-11-04 20:53:44 【问题描述】:

Reactivity in Depth 上的 Vue.js 文档提到了这一点

一个属性必须存在于数据对象中才能让 Vue 转换它并使其具有反应性

(...)

你必须通过声明所有根级别来初始化 Vue 实例 预先反应数据属性,即使只是一个空值

考虑这两个代码 sn-ps,其中tags 被定义为一个空对象并在脚本过程中以两种不同的方式更新:

var vm = new Vue(
  el: "#root",
  data: 
    tags: 
  
);

vm.tags = 
  hello: true,
  world: true
;
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.js"></script>
<div id="root">
  tags
</div>

var vm = new Vue(
  el: "#root",
  data: 
    tags: 
  
);

vm.tags["hello"] = true
vm.tags["world"] = true
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.3/vue.js"></script>
<div id="root">
  tags
</div>

在第一种情况下,内容已正确更新,而在第二种情况下 - 没有。

为什么会这样,尽管在这两种情况下tags 都是在 VM 实例化时声明的?

【问题讨论】:

【参考方案1】:

不是声明了tags对象,而是实例化Vue时tags对象上的属性不存在。

在第二种情况下,您使用索引器向tags 对象添加两个新属性。 Vue 无法检测到这些属性已被添加。

这就是$set 方法存在的原因。向对象添加新属性时,需要通过$setVue.set 进行添加

 Vue.set(vm.tags, 'hello', true)

或者,如果你在一个 Vue 方法中,

this.$set(this.tags, 'hello', true)

first 的情况下,您正在添加一个完全不同的对象,该对象 具有 属性。在这种情况下,Vue 知道属性,并在将新值添加到数据时将它们转换为 reactive 属性。

如果改为您添加了一个新的空对象并然后添加了属性,您将回到与第二个示例相同的情况。

通常,您只想用空属性初始化对象。

data: 
    tags:
        hello: false,
        world: false
    

在这种情况下,属性将被转换为反应性属性并检测到变化。

编辑

@WoJ 用钢笔发表了一条评论,代码如下:

var vm = new Vue(
  el: "#root",
  data: 
    posts: ,
    tags: 
  
);

vm.tags = 
  hello: true,
  world: true
;

vm.posts["bonjour"] = true
vm.posts["monde"] = true
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<div id="root">
  <!-- show all tags which are on -->
  tags
  posts
</div>

在这段代码中,显示 Vue 的 posts 属性使用新的响应属性进行了更新,因为它们在 Vue 渲染时显示在输出中。这里发生的是属性添加到posts对象,这就是javascript的工作原理,你可以给对象添加属性,但是Vue没有知道他们在那里。更具体地说,这些属性不会作为 reactive 属性(getter/setter)添加,这是 Vue 知道何时更改已添加到 Vue 的数据的方式。事实上,将这些属性添加到posts 对象不会触发渲染

那么,为什么新属性会出现在输出中?新的posts 属性出现在输出中的原因是因为将tags 属性设置为新对象会触发要调度的渲染。 重要的是要知道 Vue 渲染不是同步的,它们是异步的(请参阅 here)。

... Vue 执行 DOM 更新 异步。每当观察到数据变化时,它将打开一个 排队并缓冲同一事件中发生的所有数据更改 循环。

例如,当您设置 vm.someData = 'new value' 时,组件 不会立即重新渲染。它将在下一个“滴答”中更新, 当队列被刷新时。

在上面的代码示例中,对tags 的更新会触发要安排的渲染。然后,posts 对象被更新为两个新属性,这些属性没有转换为响应式属性,因为 Vue 不知道它们存在。 然后 一段时间后,预定的渲染发生,Vue 用数据中对象的 当前 状态更新 html。由于posts 确实 具有这两个新属性,因此这些属性将呈现到屏幕上。然而,对这些属性的更新将永远触发渲染更新。

要查看这种情况,只需注释掉对tags 属性的更新。

var vm = new Vue(
  el: "#root",
  data: 
    posts: ,
    tags: 
  
);

//vm.tags = 
//  hello: true,
//  world: true
//;

vm.posts["bonjour"] = true
vm.posts["monde"] = true
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js"></script>
<div id="root">
  <!-- show all tags which are on -->
  tags
  posts
</div>

注意,在这种情况下,渲染的 Vue从不改变。

【讨论】:

谢谢,我花了很多时间来调试它——也是因为 CodePen 的行为不同。 codepen.io/WoJWoJ/pen/bRGamg 例如表明我的两个例子都在工作...... @WoJ 它在你的笔中并没有真正的不同,它只是看起来像它一样。如果您在笔中进行更改后记录 vm.posts 和 vm.tags,您将看到 vm.posts not 响应式(未观察到它们)。如果您注释掉对 vm.tags 的更新,您将看到屏幕上从未呈现任何更改。这是因为对 vm.tags 的更改会导致安排渲染,并且当渲染发生时,两个属性都会显示。 非常感谢 CodePen 案例的详细描述,特别指出触发后渲染的异步特性。这一点现在很清楚了。很遗憾我不能双重投票:) @WoJ 很高兴为您提供帮助 :)【参考方案2】:

这是因为在第一种情况下,您在 tags 上运行 setter(因为您正在重新分配它) - Vue 已经包装并可以检测到它。在第二种情况下,您在 data: tags: 定义中的嵌套属性上运行 setter,因此它们不是响应式的。

文档中的Change Detection Caveats 部分涵盖了这一点,尽管与您的情况(嵌套属性情况)完全不同。您必须像这样声明您的数据:

data: 
  tags: 
    hello: null,
    world: null,
  

【讨论】:

以上是关于如何更新反应性对象(和一般属性)?的主要内容,如果未能解决你的问题,请参考以下文章

Vue 计算属性不会在更新其反应性依赖项时更新

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

使用 Vue.js 跨浏览器选项卡的反应式 localStorage 对象

反应钩子:useState/context;无法读取未定义的属性“头像”/如何更新嵌套对象

如何在Vuejs中将反应性道具传递给孩子

尝试更改对象属性并保持反应性。属性或方法“vm”未在实例上定义,但在渲染期间被引用