Как "избежать прямой мутации опоры" в рекурсивном компоненте

Как "избежать прямой мутации опоры" в рекурсивном компоненте

13.03.2020 05:51:55 Просмотров 14 Источник

Я понимаю эту концепцию: ребенок работает над своей собственной копией данных prop, и когда он изменил ее, он может $emit изменить это, чтобы родитель мог обновить себя.

Однако я имею дело с рекурсивной древовидной структурой, например файловой системой, и это хорошая аналогия.

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

У меня есть рекурсивный компонент под названием Thing, который принимает пропеллер childtree и выглядит немного так:

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

Изменение имени, очевидно, приведет к прямому изменению опоры, чего следует избегать, и Vue выдает предупреждение об этом.

Однако единственный способ, который я вижу, чтобы избежать этого, был бы для каждого компонента сделать глубокую копию childtree; затем $emit копию нашей копии и иметь @input (или такую) копию обратно в оригинал; весь путь вверх по дереву, который затем изменил бы реквизит полностью вниз по дереву, вызывая еще одну глубокую копию всего!

Такое ощущение, что это будет действительно неэффективно на дереве любого размера.

Есть ли лучший способ? Я знаю, что вы можете обмануть Vue, чтобы не выдавать пример ошибки / предупреждения jsfiddle .

У вопроса есть решение - Посмотреть?

Ответы - Как "избежать прямой мутации опоры" в рекурсивном компоненте / How to "Avoid mutating a prop directly" in a recursive component

Является ответом!
Krzysiek W

13.03.2020 08:41:05

Исправление для вашей проблемы-это передача только события из дочернего компонента. https://jsfiddle.net/kv1w72pg/2/

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

Тем не менее я настоятельно рекомендую вам использовать другое решение, такое как:

  1. Vuex,
  2. Автобус событий

Это сделает код ясным и обеспечит один источник истины.

Edit: в случае рекурсивного наследования использование provide inject было бы проще: https://jsfiddle.net/s0b9fpr8/4/

Таким образом, вы предоставляете функцию, которая может изменить состояние базового компонента для всех дочерних компонентов (функция должна быть введена).

Md. Amirozzaman

13.03.2020 09:30:15

Односторонний Поток Данных

Каждый раз, когда родительский компонент обновляется, все реквизиты в дочернем компоненте будут обновляться с последним значением. Это означает, что вы не должны пытаться мутировать опору внутри дочернего компонента. Если вы это сделаете, Vue предупредит вас в консоли.

Обратите внимание, что объекты и массивы в JavaScript передаются по ссылке, поэтому если пропеллер является массивом или объектом, то происходит мутация самого объекта или массива внутри дочернего компонента будет влиять на родительское состояние.

Вот пример с 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

Поэтому, если вы хотите обновить какое-либо значение реквизитов в вашем дочернем компоненте, вам нужно использовать cloneDeep из lodash.

artfulrobot

14.03.2020 11:08:11

Вот примерно как я справился с этим:

<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>

Это означает каждый узел дерева:

  • работает над своей собственной копией "имени"
  • содержит свой собственный массив дочерних элементов (даже если они "реальны" - это ссылки, принадлежащие объекту, который не является "нашим" для изменения, но массив является нашим).
  • говорит родителю, чтобы он заменил его, когда есть обновление.
  • прослушивает дочерние узлы, которые были обновлены.

Это, кажется, работает и, кажется, избегает мутации реквизита. Одна вещь, не показанная здесь, но я обнаружил, что она имеет решающее значение, - это установка уникального идентификатора для каждого ребенка. Я сделал это, создав идентификаторы последовательно из глобального счетчика (на $root).

Закрыть X