Как «избегать мутации напрямую» в рекурсивном компоненте - PullRequest
4 голосов
/ 13 марта 2020

Я понимаю концепцию: ребенок работает с собственной копией данных реквизита, и когда она изменяется, она может $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, чтобы не выдавать ошибку / предупреждение пример jsfiddle .

Ответы [ 3 ]

1 голос
/ 13 марта 2020

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

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

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

  1. Vuex,
  2. Шина для событий

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

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

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

0 голосов
/ 14 марта 2020

Вот как я это обработал:

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

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

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

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

0 голосов
/ 13 марта 2020

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

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

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

Вот пример с loda sh

, когда мы просто 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 от loda sh.

...