【问题标题】:Why does Vue.js allow pushing to prop array?为什么 Vue.js 允许推送到 prop 数组?
【发布时间】:2019-10-29 00:13:26
【问题描述】:

当我们尝试直接更改 prop 值时,Vue.js 会显示警告,如下所示:

Vue.component('Games', {
    template: `
        <div>
            <ol>
                <li v-for="game in games">{{ game }}</li>
            </ol>
            <button @click="clear">Clear games</button>
        </div>
    `,
    props: ['games'],
    methods: {
        clear: function () {
            this.games = [];
        }
    }
});

正在显示的警告是:

避免直接修改 prop,因为每当父组件重新渲染时,该值都会被覆盖。相反,使用基于道具值的数据或计算属性。

我知道为什么会发生这种情况,但令我惊讶的是.push() 不会发生这种情况。如果我更改方法以向数组添加值而不是重写它,则不会出现警告:

methods: {
    add: function () {
        this.games.push('Whatever');
    }
}

为什么没有警告?怎么直接推到 prop 好,重写不行?

【问题讨论】:

  • 被推送到的道具是一个数组。压入一个新值后,它仍然是一个数组。我相信 vue 不会对开箱即用的 props 进行深入观察(即,它不关心 inside 数组的内容)。
  • 同类型有什么关系?据我所知,Vue.js 甚至有自己的 Array.push 包装器,所以它会让事情变得更容易控制。据说在重新渲染的情况下更改道具是错误的。这不是关于类型,而是关于值与在 prop 中传递的值不同。
  • 我现在将发布更深入的答案。

标签: javascript vue.js


【解决方案1】:

被推送到的道具是一个数组。压入一个新值后,它仍然是一个数组。我相信 vue 不会对开箱即用的道具进行深入观察(即,它不关心数组内部的内容)。

引用this的文章;

const array = [1, 2, 3, 4];
// array = [1, 2, 3, 4]

Now you update the array by pushing some more values into it:

array.push(5);
array.push(6);
array.push(7);
// array = [1, 2, 3, 4, 5, 6, 7]

问题来了:数组有变化吗?

嗯,没那么简单。

数组的内容发生了变化,但变量数组仍然存在 指向同一个 Array 对象。数组容器没有改变, 但是数组里面的东西变了。

所以当你观察一个数组或一个对象时,Vue 并不知道你已经 改变了那个道具里面的东西。你必须告诉 Vue 你想要它 在观察变化时检查道具内部。

迈克尔·蒂森的文章, 2018 年 10 月发布 - 所有功劳归于他们。

为了回应您关于这仍然是反模式的评论,我会这样说;

如果我们首先考虑一下为什么直接在组件上修改 props 是一种反模式,那么推理仍然成立。再次引用迈克尔的话(我保证,我总是偶然发现他的东西);

“我们这样做是因为它确保每个组件都与 彼此。由此我们可以保证一些对我们有帮助的事情 考虑我们的组件:只有组件才能改变它自己的 状态。只有组件的父组件才能更改道具。”

【讨论】:

  • 我想我现在明白了,但如果我是正确的,你的回答只是部分正确。我认为这是关于道具本身的价值,在对象的情况下它只是一个参考。所以如果我们改变数组,引用不会改变——它仍然是内存中的同一个点。我想知道这是否仍然是直接改变道具的反模式,即使它们是对象。
  • 将此评论移至答案中。
  • 是的,这完全有道理——我相信组件的主要目的是自主的。随着 Array.push 在道具上它们不再是。当这些事情发生在 JavaScript 中时,我总是感到有点失望——你需要小心这种松散的语言。
【解决方案2】:

这只是因为Array 是一种参考内存。当您将ArrayObject 存储在任何变量中时,它就是一个引用变量。

Memory management 在这种情况下,引用变量将指向heap 中的内存地址,因此您可以向地址添加更多值。但是,即使使用新地址,您也不能只用任何新值替换该地址。

【讨论】:

  • 是的,这就是我在提出问题后不久对自己的看法(因此我在 Lewis 的回答下发表了评论。你还会认为直接推送到 props 是一种反模式吗?
  • 我认为你更新的部分实际上把答案搞砸了。说它是一个引用就足够了,而 Vue 警告的是改变值,这意味着该值是一个可变的低级内容。起初我以为 Vue 想要警告一个反模式,但看起来这个警告纯粹是技术性的。可以请您删除已编辑的部分吗?它没有增加任何东西,使它更加混乱。这个问题与深度克隆无关。
  • 啊 - 有效,它会混淆 - 这正是我想避免这种情况的重点 - 你是对的!删除谢谢
  • 如何克隆一个对象是另一个故事的主题。
【解决方案3】:

正如警告所说,会导致意外行为

当组件更新/渲染时,props 会作为值写入底层组件模型。这可能在“任何”时间再次发生。这通常会发生在以 v-if 或 v-for 条件呈现的组件上。在这种情况下,prop 值会再次从父级写入子级。

如果子级增加计数器,它只会增加它的本地副本,原始值parent.data.counter 仍将保持在值 5。如果父级被更新(例如通过设置 counter=counter-1)然后 Vue将使用父级的新值覆盖子级中的值。因此,孩子的所有变化都会丢失 - 这可能会导致意外行为。

这个问题不会发生,如果prop是一个引用,它是变异的

如果 prop 是一个数组,那么内存中只存在这个数组的一个副本。数组的任何突变都会改变父数组和子数组中的原始数组。如果您愿意,这仅适用于数组本身的突变,例如试试child.propArray = child.propArray.slice(3) 你会遇到和以前一样的问题。由于 slice 不会更改原始数组,而是创建一个副本,因此子节点的引用将与父节点不同,因此可能会出现意外行为。

这是一个演示问题的代码小提琴:

Vue.component('child-comp', {
  // camelCase in JavaScript
  props: ['counter', 'arr'],
  template: `<h3>Child Counter: {{ counter }} <button @click="inc">+</button>
     -- Array: {{arr}} <button @click="push">push</button></h3></h3>`,
  methods: { inc() { this.counter++ }, push() { this.arr.push(this.arr.length+1) } }
})

new Vue({
  el: '#main',
  data: { counter: 1, arr: [] },
  methods: { inc() { this.counter++ }, push() { this.arr.push(this.arr.length+1) } }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<main id="main">
   <h3>Main Counter: {{counter}} <button @click="inc">+</button>
     -- Array: {{arr}} <button @click="push">push</button></h3>
   <child-comp :counter="counter" :arr="arr"></child-comp>
   <child-comp :counter="counter" :arr="arr"></child-comp>
</main>

【讨论】:

  • 所以如果你真的想改变一个孩子中的一个道具,你可以将它包装在一个对象中,并将该对象作为道具传递。 - Vue 的基本理念是让子级向父级发出消息并更改原始值而不是改变 props。这对于简单的任务非常有用,但是如果您通过一系列嵌套组件传递复杂的对象,那么将这些事件一直冒泡会使您的代码非常难以阅读和调试。 - 替代方案是 VueX 或代码库中数据的良好所有权模型。
猜你喜欢
  • 2019-06-08
  • 2018-04-17
  • 2020-08-10
  • 1970-01-01
  • 2017-12-28
  • 1970-01-01
  • 2010-11-28
  • 2011-06-04
相关资源
最近更新 更多