Vue и Vuex: обработка зависимых вычисляемых свойств


Мое приложение-это сокращенная электронная таблица, встроенная в Vue с Vuex. Ключевыми компонентами являются:TableCollection, Table и Row. TableCollection имеет массив с несколькими объектами Table. Каждый Table имеет массив с несколькими объектами Row.

Компонент Row имеет свойство с именем calculatedField. Это просто объединяет два поля в строке, чтобы создать третье поле. Мои варианты-реализовать calculatedField как вычисляемое свойство, локальное для компонента Row, или как геттер в Vuex магазин.

Компонент Table требует значения subTotal, которое вычисляется путем добавления значения calculatedField для всех строк в таблице. Как вы можете видеть, вычисление subTotal зависит от вычисления calculatedField.

Если я реализую calculatedField как локальное вычисляемое свойство Row, оно кэшируется. Проблема, однако, в том, что я не могу получить доступ к вычисляемому полю от родителя Table. Я попробовал следующее в Table:

computed : {
    subTotal : function () {
        let total = 0;
        this.table.rows.forEach(function (row) {
           total += row.calculatedField;
        });
        return total;
    }
}

В результате была Нэн.

Одно решение было бы следует дублировать логику из calculatedField в вычисляемом свойстве Table, но это не сухо.

Другой альтернативой было бы реализовать как subTotal, так и calculatedField в качестве геттеров в хранилище, однако это означало бы передачу аргументов геттеру (tableId, rowId, или и то и другое), и поэтому результаты не будут кэшироваться. Это кажется действительно неэффективным.

Последняя возможность заключается в том, что я реализую свою логику calculatedField в глобальном помощнике или миксине. Это позволит избежать дублирования кода и геттера неэффективность, но не совсем правильно-код относится конкретно к Table и Row, и в идеале будет сохранен там. Есть ли другие решения, которые я упустил из виду? Что такое идеальный "Vue-way"?
1 2

1 ответ:

Если производительность является проблемой и кэширование важно прямо сейчас, вы можете реализовать кэширование на компоненте Table .

В компонентестроки выделите новое значение, чтобы родительский компонент мог кэшировать его.

  computed: {
    calculatedField() {
      const result = this.data.field + this.data.other;
      this.$emit('change', this.data.id, result);
      return result;
    }
  },

В компонентеTable обработайте событие и кэшируйте новые значения.

  data() {
    return { cache: {} };
  },
  computed: {
    subTotal() {
      return Object.values(this.cache).reduce((total, value) => total + value, 0);
    }
  },
  methods: {
    onChange(rowId, val) {
      // important for reactivity
      this.$set(this.cache, rowId, val);
    }
  },

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

В следующем примере можно увидеть, что вычисляемые свойства выполняются один раз, а затем при изменении строки (нажмите кнопку Rand) обновляются только соответствующие вычисляемые свойства.

const MyRow = {
  props: {
    data: {
      type: Object
    }
  },
  computed: {
    calculatedField() {
      console.log("row computed for", this.data.id);
      const result = this.data.field + this.data.other;
      this.$emit('change', this.data.id, result);
      return result;
    }
  },
  methods: {
    onClick() {
      this.data.other = Math.floor(Math.random() * 10);
    }
  },
  template: `
    <tr>
        <td>{{ data.field }}</td>
        <td>{{ data.other }}</td>
        <td>{{ calculatedField }}</td>
        <td><button type="button" @click="onClick">Rand</button></td>
    </tr>
  `
};

const MyTable = {
  props: {
    rows: {
      type: Array
    }
  },
  components: {
    MyRow
  },
  data() {
    return {
      cache: {}
    }
  },
  computed: {
    subTotal() {
      console.log("Table subTotal");
      return Object.values(this.cache).reduce((total, value) => total + value, 0);
    }
  },
  methods: {
    onChange(rowId, val) {
      console.log("onChange", rowId, val);
      this.$set(this.cache, rowId, val);
    }
  },
  template: `
    <div>
        <table border="1">
            <tr><th>field</th><th>other</th><th>calculated</th><th></th></tr>
            <my-row v-for="row in rows" @change="onChange" :key="row.id" :data="row"></my-row>
        </table>
        Subtotal: {{ subTotal }}
    </div>
  `
};

var app = new Vue({
  el: '#app',
  components: {
    MyTable
  },
  data: {
    rows: [{
        id: 1,
        field: 1,
        other: 1
      },
      {
        id: 2,
        field: 2,
        other: 2
      },
      {
        id: 3,
        field: 3,
        other: 3
      },
    ]
  },
  template: `<my-table :rows="rows"></my-table>`
});
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.4.2/vue.min.js"></script>