如何优化 table 上表示按列组织的数据的数据绑定

How to optimize data binding on table representing data organized as columns

在使用 table 显示按列组织的数据时,我不知道如何正确使用计算属性。 jsfiddle 提供了我的代码的完整示例,这里是简短版本和说明。 我想将此数据呈现为 table:

var vueData = {
  objects: [
    {
      name: "objectName",
      objectData: [
        {prop1: "val1", prop2: "val2"}
      ]
    }
  ]
}

在此vueData中,objectData数组的每个元素应显示为一列(该列代表一个月或一天的数据)。 objectData 元素中的道具不应按原样显示,而应显示为计算值。并且显示的值应该反映 vueData.

的变化

所以我来到了这个vue模板:

<table>
<tr>
  <th>Object Name</th>
  <th>Data Piece 1</th>
  <th>Data Piece 2</th>
</tr>
<template v-for="obj in objects">
<tr> 
  <th rowspan="2">{{ obj.name }}</th>
  <td v-for="dataPiece in obj.objectData">{{ compute(dataPiece.prop1) }}</td>
</tr>
<tr><td v-for="dataPiece in obj.objectData">{{ compute(dataPiece.prop2) }}</td></tr>
</template>
</table>

一切正常,除了我使用的是方法,而不是 vue 的计算属性。方法的问题是它们的结果不会缓存,并且在单个道具更改后 ALL 单元格的值会重新计算。

我可以为每个具有计算 属性 而不是 tds 的单元格使用 vue 组件,但它看起来有点矫枉过正,因为 table 可能很大。

从vue的角度来看,还有其他解决方案吗? (我的意思是不改变 table 等的数据结构或外观)。

我在 LinusBorg Generating computed properties on the fly 那里看到了这个 post,他在其中展示了一个将属性映射到计算属性的函数。

我为您的 objects 变量调整了该函数,因为原始函数更侧重于平面数据(也有点吓人)。

function mapObjectToComputed({objects}) {
  console.log(objects)
  let res = {};
  objects.forEach((obj,i) => {
    obj.objectData.forEach((dat,j) => {
      ['prop1', 'prop2'].forEach(prop => {
        const propModel = `objects_${i}_${j}_${prop}`;
        const computedProp = {
          get() {
            console.log(`Getting ${propModel}`)
            const val = this.objects[i].objectData[j][prop];
            return val;
          }
        }
        res[propModel] = computedProp;
      })
    })
  })
  return res;
}

这是模板的内部部分。我已将 prop1 更改为新语法,并保留了 prop2 的原样。

<template v-for="(obj, i) in objects">
  <tr> 
    <th rowspan="2">{{ obj.name }}</th>
    <td v-for="(dataPiece, j) in obj.objectData">
      {{ fetch(i,j,'prop1') }}
    </td>
  </tr>
  <tr><td v-for="dataPiece in obj.objectData">
    {{ compute(dataPiece.prop2) }}
  </td></tr>
</template>

组件是

var vue = new Vue({
    el: document.getElementById("vue"),
  data: vueData,
  methods: {
    fetch: function(i,j,prop) {
      const propModel = `objects_${i}_${j}_${prop}`
      return this[propModel];
    },
    compute: function(prop) {
        console.log('computing ' + prop);
        return 'computed(' + prop + ')';
    }
  },
  computed: {
    firstProp: function() {
        return this.objects[0].objectData[0].prop1;
    },
    ...mapObjectToComputed(vueData)
  }
});

超时后的控制台为

Getting objects_0_0_prop1  
computing 1-1-2  
computing 1-2-2  
computing 1-3-2  
computing 2-1-2  
computing 2-2-2  
computing 2-3-2  
computing 3-1-2  
computing 3-2-2  
computing 3-3-2  

所以只有 prop2 全面重新计算了。

这是Fiddle

正如您所说,您可以使用一个组件。这是我找到的最干净的解决方案。该组件非常简单,已针对此示例进行了调整。如果计算特别昂贵,它可能值得使用。

var vueData = {
  objects: [{
      name: 'Object 1',
      objectData: [{
          prop1: '1-1-1',
          prop2: '1-1-2'
        },
        {
          prop1: '1-2-1',
          prop2: '1-2-2'
        },
        {
          prop1: '1-3-1',
          prop2: '1-3-2'
        }
      ]
    },
    {
      name: 'Object 2',
      objectData: [{
          prop1: '2-1-1',
          prop2: '2-1-2'
        },
        {
          prop1: '2-2-1',
          prop2: '2-2-2'
        },
        {
          prop1: '2-3-1',
          prop2: '2-3-2'
        }
      ]
    },
    {
      name: 'Object 3',
      objectData: [{
          prop1: '3-1-1',
          prop2: '3-1-2'
        },
        {
          prop1: '3-2-1',
          prop2: '3-2-2'
        },
        {
          prop1: '3-3-1',
          prop2: '3-3-2'
        }
      ]
    },
  ]
};

var vue = new Vue({
  el: document.getElementById("vue"),
  data: vueData,
  methods: {
    compute: function(prop) {
      console.log('computing ' + prop);
      return 'computed(' + prop + ')';
    }
  },
  components: {
    cacheResult: {
      props: {
        fn: Function,
        arg: String
      },
      template: '<td>{{fn(arg)}}</td>'
    }
  },
  computed: {
    firstProp: function() {
      return this.objects[0].objectData[0].prop1;
    }
  }
});

setTimeout(function() {
  vueData.objects[0].objectData[0].prop1 = 'changed on timeout';
}, 3000);
th,
td {
  border: 1px solid black;
}
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="vue">
  <table>
    <tr>
      <th>Object Name</th>
      <th>Data Piece 1</th>
      <th>Data Piece 2</th>
      <th>Data Piece 3</th>
    </tr>
    <template v-for="obj in objects">
      <tr>
        <th rowspan="2">{{ obj.name }}</th>
        <td v-for="dataPiece in obj.objectData"
          is="cacheResult"
          :fn="compute"
          :arg="dataPiece.prop1"
          >
        </td>
      </tr>
      <tr>
        <td v-for="dataPiece in obj.objectData"
          is="cacheResult"
          :fn="compute"
          :arg="dataPiece.prop2">
        </td>
      </tr>
    </template>
  </table>
  <span>Computed prop1 value = {{ firstProp }}</span>
</div>

我本来想用一个指令来做到这一点,但没有好的方法来传递函数和参数,直到我想起我可以使用 vnode.context 来获取上下文,在那里我可以通过以下方式查找函数名称而不是将其作为实际函数传递。

所以这是一个指令,它使用函数调用的结果更新其元素的 textContent

var vueData = {
  objects: [{
      name: 'Object 1',
      objectData: [{
          prop1: '1-1-1',
          prop2: '1-1-2'
        },
        {
          prop1: '1-2-1',
          prop2: '1-2-2'
        },
        {
          prop1: '1-3-1',
          prop2: '1-3-2'
        }
      ]
    },
    {
      name: 'Object 2',
      objectData: [{
          prop1: '2-1-1',
          prop2: '2-1-2'
        },
        {
          prop1: '2-2-1',
          prop2: '2-2-2'
        },
        {
          prop1: '2-3-1',
          prop2: '2-3-2'
        }
      ]
    },
    {
      name: 'Object 3',
      objectData: [{
          prop1: '3-1-1',
          prop2: '3-1-2'
        },
        {
          prop1: '3-2-1',
          prop2: '3-2-2'
        },
        {
          prop1: '3-3-1',
          prop2: '3-3-2'
        }
      ]
    },
  ]
};

var vue = new Vue({
  el: document.getElementById("vue"),
  data: vueData,
  methods: {
    compute: function(prop) {
      console.log('computing ' + prop);
      return 'computed(' + prop + ')';
    }
  },
  directives: {
    cache(el, binding, vnode) {
      if (binding.value !== binding.oldValue) {
        el.textContent = vnode.context[binding.arg](binding.value);
      }
    }
  },
  computed: {
    firstProp: function() {
      return this.objects[0].objectData[0].prop1;
    }
  }
});

setTimeout(function() {
  vueData.objects[0].objectData[0].prop1 = 'changed on timeout';
}, 3000);
th,
td {
  border: 1px solid black;
}
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="vue">
  <table>
    <tr>
      <th>Object Name</th>
      <th>Data Piece 1</th>
      <th>Data Piece 2</th>
      <th>Data Piece 3</th>
    </tr>
    <template v-for="obj in objects">
      <tr>
        <th rowspan="2">{{ obj.name }}</th>
        <td v-for="dataPiece in obj.objectData"
          v-cache:compute="dataPiece.prop1"
          >
        </td>
      </tr>
      <tr>
        <td v-for="dataPiece in obj.objectData"
          v-cache:compute="dataPiece.prop2">
        </td>
      </tr>
    </template>
  </table>
  <span>Computed prop1 value = {{ firstProp }}</span>
</div>