如何在递归组件中“避免直接更改道具”

时间:2020-03-13 14:51:55

标签: javascript vue.js vuejs2 vue-component

我理解这个概念:孩子在自己的道具数据副本上工作,并且在更改后可以$emit进行更改,以便父级可以自行更新。

但是,我正在处理一个递归树结构,例如一个文件系统,是一个很好的类比。

[
 { type: 'dir', name: 'music', children: [
    { type: 'dir', name: 'genres', children: [
       { type: 'dir', name: 'triphop', children: [
          { type: 'file', name: 'Portishead' },
          ...
       ]},
     ]}
   ]}
]}

我有一个名为Thing的递归组件,它使用childtree prop 并看起来像这样:

<template>
  <input v-model="tree.name" />
  <thing v-for="childtree in tree.children" :tree="childtree" ></thing>
</template>

修改名称显然会直接修改道具,这是可以避免的,Vue会发出警告。

但是,我唯一能避免的方法是让每个组件都对childtree进行深层复制。然后$emit份我们的副本,并@input(或类似的副本)将其复制回原件;一直到树上,这将改变树下的道具,导致所有事物的另一个深层复制!

这感觉像在任何大小的树上都效率很低。

有更好的方法吗?我知道您可以欺骗Vue不发出错误/警告example jsfiddle

3 个答案:

答案 0 :(得分:1)

解决您的问题是从子组件传递公正事件。 https://jsfiddle.net/kv1w72pg/2/

<input @input="(e) => $emit('input', e.target.value)" />

话虽如此,我强烈建议您使用其他解决方案,例如:

  1. Vuex,
  2. 事件总线

这将使代码清晰并确保真理的一种来源。

编辑:在递归继承的情况下,使用提供注入会更容易:https://jsfiddle.net/s0b9fpr8/4/

通过这种方式,您提供了可以将基本组件的状态更改为所有子组件的函数(必须注入函数)。

答案 1 :(得分:0)

单向数据流

每次更新父组件时,子组件中的所有道具都会以最新值刷新。这意味着您不应该尝试在子组件内部变异道具。如果这样做,Vue将在控制台中警告您。

请注意,JavaScript中的对象和数组是通过引用传递的,因此 如果prop是数组或对象,则使对象或数组本身发生突变 子组件中的元素会影响父状态。

以下是lodash

的示例

当我们clone时。

var objects = [{ 'a': 1 }, { 'b': 2 }];

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true

当我们使用cloneDeep

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

因此,如果要更新子组件中的任何props值,则需要使用lodash的cloneDeep

答案 2 :(得分:0)

大致是我的处理方式:

<template>
  <div>
    <input v-model="myName" @input="updateParent()" />
    <thing v-for="(child, i) in myChildren" :key="child.uniqueId"
     @input="setChild(i, $event)"
     >
    </thing>
  </div>
</template>
<script>
export default {
props: ['node'],
data() {
  return {
     myName: this.node.name,
     myChildren: this.node.children.slice(0),
  };
},
methods: {
  updateParent() {
    this.$emit('input', {name: this.myName, children: this.myChildren});
  },
  setchild(i, newChild) {
    this.$set(this.myChildren, i, newChild);
  }
}
}
</script>

这意味着树的每个节点:

  • 使用它自己的“名称”副本
  • 拥有自己的子级数组(即使子级是“真实的”-它们是属于对象的引用,该对象不是“我们的”要更改的,而是数组是我们的)。
  • 告诉父母在更新时将其替换。
  • 收听已更新的子节点。

这似乎可行,并且似乎避免了道具的变异。此处未显示但我发现很关键的一件事是为每个孩子设置唯一的ID。我是通过从全局计数器(在$root上)顺序创建ID来实现的。