javascript - Vue 2 从嵌套组件更新数组中对象的属性

标签 javascript vue.js vuejs2 vue-component quasar-framework

我从事 Vue 2 项目已有一段时间了,在升级我们的 linting 要求后,我发现我们有 prop我们许多子组件中的突变错误。在我们的项目中,我们将一个单例对象作为 prop 传递给许多组件,并且最初是直接从子组件更新对象。 Vue seems to suggest using the v-bind.sync feature用于更新 props来自子组件(或使用等效的 v-bindv-on )。然而,这并没有解决 prop 的问题。从数组中的嵌套组件修改。

以使用 prop 的(伪)代码为例突变:

注意:假设 const sharedObject: { arrayElements: Array<{ isSelected: boolean }> } = ...

页面.vue

<template>
  ...
  <Component1 :input1="sharedObject" />
  ...
</template>

Component1.vue

<template>
  ...
  <template v-for="elem in sharedObject.arrayElements">
    <Component2 :input2="elem" />
  </template>
  ...
</template>

Component2.vue

<template>
  ...
  <q-btn @click="input2.isSelected = !input2.isSelected"></q-btn>
  ...
</template>

更新 input2.isSelected 等属性的正确方法是什么?来自 Vue 2 中的嵌套组件?我想到的所有方法都有缺陷。

有缺陷的方法

相信我们想冒出那​​个input2.isSelected已在 Component2 中修改至 Page.vue ,但是,这似乎要么导致代码困惑,要么让人觉得我们只是在以一种迂回的方式抑制 linting 错误。


为了演示“乱码”方法,首先注意 Page.vue不知道 elem 的索引在sharedObject.arrayElements .因此,我们需要向 Page.vue 发射一个对象来自 Component1其中包含 input2.isSelected 的状态以及 elem 的索引在sharedObject.arrayElements .这很快就会变得困惑。我们的例子怎么样:

Component1.vue

<template>
  ...
  <template v-for="elem in sharedObject.arrayElements">
    <template v-for="elem2 in elem.arrayElements">
       <Component2 :input2="elem2" />
    </template>
  </template>
  ...
</template>

在这种情况下,我们可能需要传递 2 个索引!这对我来说似乎不是一个可持续的解决方案。


我想到的替代方案是一个回调函数(作为 prop 通过组件层次结构传递),它将我们要更新的元素和包含我们要更新的属性的对象作为输入(使用 Object.assign ) .

这让我非常感到不安,因为我不知道我们不能从子组件更新传递引用属性的真正原因。对我来说,这似乎只是一种从 Component2 更新传入值的迂回方式。没有 Lint 注意到。如果 Prop 在传递给子组件时发生了一些神奇的修改,那么肯定会传递我在 Component2 中收到的对象。到回调函数并在父组件中修改它基本上只是更新子组件中的 prop,但更复杂。

在 Vue 2 中解决这个问题的正确方法是什么?

最佳答案

非常好的问题和对 Vue 生态系统中这个长期存在问题的现状的分析。

是的,修改来自 child 的“值类型” Prop is a problem因为它会产生运行时问题(父级在重新渲染时覆盖更改),因此 Vue 在发生这种情况时会生成运行时错误...

从“代码工作正常”的 POV 可以修改作为 prop 传递的对象的属性。不幸的是,社区中有一些有影响力的人(以及许多盲目追随他们的人)认为这是一种反模式。我不同意这一点并多次提出我的论点(例如 here )。您很好地描述了原因 - 它只会产生不必要的复杂性/样板代码......

所以你正在处理的实际上只是一个 linting 规则 ( vue/no-mutating-props )。正在进行 issue/discussion它提出了配置选项,该选项应该允许通过许多好的论据来减轻规则的严格性,但它很少受到维护者的关注(也可以在那里提高你的声音)

现在你可以做的是:

  1. 禁用规则(远非完美,但幸运的是,由于 Vue 运行时错误,您可以在开发过程中捕捉到真正的错误情况)
  2. 接受现实并使用变通办法

解决方法——使用全局状态(像 Vuex 或 Pinia 这样的存储)

注意:Pinia 是首选,因为下一个版本的 Vuex 将具有相同的 API

一般的想法是将 sharedObject 放在商店中,并仅使用 Prop 将子组件导航到正确的对象 - 在您的情况下,Component2 将收到一个索引通过 prop 并使用它从商店中检索正确的元素。

Stores 非常适合共享全局状态,但使用它只是为了克服 linting 规则是不好的。此外,组件与商店耦合,因此可重用性受到影响,测试也更加困难

解决方法 - 事件

是的,仅使用事件可能会造成困惑和大量样板代码(尤其是当您嵌套组件超过 2 层时),但有一些方法可以使事情变得更清晰。

例如,在您的情况下,Component2 不需要知道索引,因为您可以像这样处理事件

// Component1
<template>
  ...
   <template v-for="elem in sharedObject.arrayElements">
    <template v-for="(elem2, index) in elem.arrayElements">
       <Component2 :input2="elem2" @update="updateElement($event, index)" />
    </template>
  </template>
  ...
</template>

在您的情况下,Component2 仅处理单个 bool 属性的更改,因此 $event 可以是简单的 bool 值。如果 Component2 中有多个属性要更改,$event 可以是一个对象,您可以使用对象扩展语法来“简化”Component2(使用一个事件而不是多个事件 - 每个属性一个)

// Component2
<template>
  ...
  <input v-model="someText" type="text">
  <q-btn @click="updateInput('isSelected', !input2.isSelected)"></q-btn>
  ...
</template>
<script>
export default {
  props: ['input2'],
  computed: {
    someText: {
      get() { return this.input2.someText },
      set(newVal) { updateInput('someText', newVal) }
    }
  },
  methods: {
    updateInput(propName, newValue) {
      const updated = { ...this.input2 } // make a copy of input2 object
      updated[propName] = newValue  // change selected property

      this.$emit('update', updated) // send updated object to parent
    }
  }
}
</script>

好吧...我更喜欢禁用规则并设置一些明确的命名约定来指示组件负责更改其输入...

请注意,还有其他解决方法,例如使用 this.$parentinject\provide 或事件总线,但这些非常糟糕

关于javascript - Vue 2 从嵌套组件更新数组中对象的属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70088266/

相关文章:

javascript - 一种在另一个 div 打开时隐藏一个 div 的方法

javascript - Gmail 文件上传进度条中的 Flash

javascript - 如何处理 v-if 中的点击项目

javascript - 带 lodash 的嵌套集合过滤器

javascript - 我应该在 PWA 中的哪个位置加载一个较大的 JSON 文件?

vue.js - 如何使用 vee-validate 在 vuetify stepper 中验证表单

javascript - 当我想传递参数时,路由器推送在 vuejs 中不起作用

javascript - Bootstrap Vue, <b-table> 带有基于表格绑定(bind)项数据的复选框输入

vue.js - 在子组件之间传递数据

vue.js - 尝试在同一页面上显示未经身份验证和经过身份验证的内容时闪烁的内容