Vue和Vuex:处理依赖的计算属性

时间:2018-04-09 23:06:00

标签: javascript vue.js vuejs2 vuex

我的应用程序是一个内置于Vue with Vuex的简化电子表格。关键组件是TableCollectionTableRowTableCollection有一个包含多个Table个对象的数组。每个Table都有一个包含多个Row个对象的数组。

Row组件有一个名为calculatedField的属性。这简单地组合了行中的两个字段以产生第三个字段。我的选择是将calculatedField实现为计算属性,Row组件的本地属性,或者作为Vuex商店中的getter。

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

结果是NaN。

一种解决方案是在calculatedField的计算属性中复制Table中的逻辑,但这不是DRY。

另一种选择是将subTotalcalculatedField同时作为商店中的getter实现,但这意味着将参数传递给gettter(tableIdrowId,或两者),因此结果不会被缓存。这似乎效率很低。

最后一种可能性是我在全局帮助器或mixin中实现我的calculatedField逻辑。这样可以避免代码重复和getter效率低下,但感觉不太正确 - 代码与TableRow具体相关,最好保留在那里。

我忽略了其他解决方案吗?什么是理想的“Vue-way”?

1 个答案:

答案 0 :(得分:2)

如果性能问题且缓存现在很重要,您可能希望Table组件上实施缓存

组件中,发出新值,以便父组件可以缓存它。

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

组件中,处理事件并缓存新值。

  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会跟踪计算出的值,使用此缓存获取小计。< / p>

您可以在以下示例中看到计算属性被命中一次,然后在行更改(单击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>