Vuex中可以使任意对象具有反应性吗?

时间:2019-07-03 03:50:25

标签: javascript vue.js vuejs2

我正在寻找通过关系优化归一化对象排序的方法。让我们假设一个应用程序需要排序 我有一个Vuex商店,其中包含许多标准化的对象,如下所示:

state: {
    worms: {
        3: { id: 3, name: 'Slurms McKenzie', measurements: [1, 6, 9]  },
        4: { id: 4, name: 'Memory worm',  measurements: [3, 4, 12] },
        6: { id: 6, name: 'Alaskan Bull Worm', measurements: [5, 7, 14]},
        ...
    },
    measurements: {
        1: { id: 1, length: 5.2, timestamp: ...},
        2: { id: 2, length: 3.4, timestamp: ...},
        3: { id: 3, length: 5.4, timestamp: ...},
        ...
    },
};

说我需要对worms上的timestamp进行排序,以使其达到最大长度。着迷于Vue的反应性,我希望能够在每种蠕虫上定义一种吸气剂,如下所示:

const getters = {
    longestLength: {
        get() { return $store.getters
                    .measurements(this.measurements)
                    .sort(...)[0] },
     },

     timestampForLongest: {
         get() { return this.longestLength.timestamp }
}

worm.extend(getters);

然后我可以轻松快速地对timestampForLongest进行排序,假设该值已被缓存。

我有一个很好的切入点来称呼这个extend(或最终被称为的东西),但是我有一些挑战。

  1. 我现在处理此问题的方式是通过计算非规范化映射,然后基于此进行排序。我在Chrome上使用的第8代Intel处理器的延迟约为700毫秒,我真的想减少这种延迟。
  2. 我不知道如何手动调用Vue的反应系统。我相信我需要定义调用类似measurement.__ob__.dep.depend()之类的吸气剂,但是我并没有为此而烦恼。
  3. 用于实现此目的的API可能是私有的,可能会更改。 Vue太慢了,无法处理800多行吗?
  4. 我不知道如何将Vuex商店($store)保留在吸气剂范围内。我可能可以使用箭头功能,所以我对此并不担心。

我可以使用Vue在普通javascript对象中按需计算和缓存值吗?

5 个答案:

答案 0 :(得分:0)

您提供的代码有语法错误,必须予以纠正:

const states = {
  worms: {
    3: {
      id: 3,
      name: 'Slurms McKenzie',
      measurements: [1, 6, 9]
    },
    4: {
      id: 4,
      name: 'Memory worm',
      measurements: [3, 4, 12]
    },
    6: {
      id: 6,
      name: 'Alaskan Bull Worm',
      measurements: [5, 7, 14]
    }
  },
  measurements: {
    1: {
      id: 1,
      length: 5.2,
      timestamp: 'ts1'
    },
    2: {
      id: 2,
      length: 3.4,
      timestamp: 'ts2'
    },
    3: {
      id: 3,
      length: 5.4,
      timestamp: 'ts3'
    },
  }
}

const store = new Vuex.Store({
  state: states,
  getters: {
    getWorms: state => {
      return state.worms
    },
    getLongestLengthByMeasurementId: state => ids => {
      const mapped = ids.map(id => {
        const measurement = state.measurements[id]
        if (measurement) {
          return {
            length: measurement.length || 0,
            timestamp: measurement.timestamp || 0
          }
        } else {
          return {
            length: 0,
            timestamp: 0
          }
        }
      })
      return mapped.find(item => item.length === Math.max.apply(null, mapped.map(item => item.length))).timestamp
    }
  },
  mutations: {
    // setting timestamp in store.state.worms[wormId]
    setLongestLength(state, wormId) {
      if (state.worms[wormId] && typeof state.worms[wormId].timestamp !== 'undefined') {
        // update the timestamp
      } else {
        // get and set the timestamp 
        const ts = store.getters.getLongestLengthByMeasurementId(state.worms[wormId].measurements)
        Vue.set(state.worms[wormId], 'timestamp', ts)
      }
    },
  },
  actions: {
    // set timestamp worm by worm
    setLongestLength({
      commit
    }, wormId) {
      Object.keys(store.getters.getWorms).forEach(key =>
        commit('setLongestLength', parseInt(key, 10))
      )
    }
  }
})

const app = new Vue({
  store,
  el: '#app',
  mounted() {
    store.dispatch('setLongestLength')
    console.log('worms', store.state.worms)
  }
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<div id="app">
  <div v-for="worm in $store.state.worms">Timestamp by worm (ID {{worm.id}}): {{worm.timestamp}}</div>
</div>

如果您还使用get:,则只需在自己的吸气剂中添加set:

答案 1 :(得分:0)

这可能会有所帮助。我不知道您项目的整个结构,但是我试图在这里重新创建,这是一种方法。

您已定义了一个state,其中包含wormsmeasurements列表。每个worm都有一个度量指标列表,我认为它与measurements列表相关。

现在state应该在Vuex store的内部定义。现在,您的store将包含四个主要元素,包括:状态,获取器,操作和突变。

因此,state本质上可以视为整个应用程序的单一事实来源。 但是我们的组件和路由如何访问存储在状态中的数据?那么,getters会将数据从store返回到我们的组件,在这种情况下,我们想要获取sortedByTSDecsortedByTSAsc方法。 因此,现在您已经了解了如何从state 获取数据,让我们看看如何设置数据到state中。您必须认为我可以定义设置器,对吧?好吧,不,Vuex “设置器” 的命名略有不同。您应该定义一个mutation才能将数据设置到state中。 最后,actionsmutations类似,但是它们没有直接突变state,而是提交了一个突变。 感到困惑吗?只需考虑一下actions,例如异步功能,而mutations同步。 在此示例中,我不知道蠕虫数据的生成位置,它可能来自其他服务器,数据库等等。因此,generateData操作将请求并等待数据,并且在数据准备好后将调用populate变异来填充state

Worn类呢?

这是魔术发生的地方。 Vue.extend()方法创建基本Vue构造函数的 子类 但是为什么? ?因为此子类具有data选项。当我们使用生成的磨损数据在populate突变中设置此值时。换句话说,state.worms包含Worn对象的列表。 另外,我们声明computed属性,以使用实例数据计算longestLengthtimestampForLongest

现在,如果要按最长长度的时间戳对worms列表进行排序,则首先需要计算最长长度,然后使用.sort()方法。默认情况下,此方法将值排序为字符串。因此,我们需要提供一个比较功能。比较功能的目的是定义替代的排序顺序。该函数应根据参数返回负,零或正值。在这种情况下,我们使用b.timestampForLongest - a.timestampForLongest降序,但是您可以使用a.timestampForLongest - b.timestampForLongest升序。

这是一个基本代码段:

const randomDate = function (start, end) {
  return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())).getTime()/1000;
};

const Worm = Vue.extend({
  computed: {
    longestLength() {
      let longest;
      for (const id of this.measurements) {
        const measurement = store.state.measurements[id];
        if (!longest || measurement.length > longest.length) {
          longest = measurement;
        }
      }

      return longest;
    },
    timestampForLongest() {
      return this.longestLength.timestamp
    },
  },
});


const store = new Vuex.Store({
  state: {
    worms: {},
    measurements: {},
  },
  actions: {
    generateData({commit}) {
      const worms = [];
      for (let w = 0; w < 800; ++w) {
        const measurements = []
        for (let m = 0; m < 3; ++m) {
          measurements.push({
            length: Math.round(Math.random() * 100) / 10,
            timestamp: randomDate(new Date(2018, 1, 1), new Date()),
          });
        }
        worms.push({
          id: w,
          name: 'Worm Name ' + w,
          measurements,
        });
      }

      commit('populate', worms)
    }
  },
  mutations: {
    populate(state, worms) {
      const wormList = {};
      const measurementList = {};
      let measurementId = 0;

      for (let worm of worms) {
        const measurementIds = [];
        for (let measurement of worm.measurements) {
          measurementId++
          measurementIds.push(measurementId)
          measurementList[measurementId] = {
            id: measurementId,
            ...measurement,
          }
        }

        wormList[worm.id] = new Worm({
          data: {
            ...worm,
            measurements: measurementIds,
          }
        });
      }

      state.worms = wormList;
      state.measurements = measurementList;
    }
  },
  getters: {
    sortedByTSDec(state) {
      return Object.values(state.worms).sort((a, b) => b.timestampForLongest - a.timestampForLongest);
    },
    sortedByTSAsc(state) {
      return Object.values(state.worms).sort((a, b) => a.timestampForLongest - b.timestampForLongest);
    },
  },
});

const app = new Vue({
  el: '#app',
  store,
  computed: {
    sortedState() {
      return this.$store.getters.sortedByTSDec;
    }
  },
  methods: {
    calculate() {
      this.$store.dispatch('generateData');
    },
    timestamp2Date(ts) {
      let newDate = new Date();
      newDate.setTime(ts * 1000);
      return newDate.toUTCString();
    }
  },
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.10/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.1.1/vuex.min.js"></script>
<div id="app">
  <button v-on:click="calculate">Get the Longest</button>
  <div v-for="worm in sortedState">
    {{ worm.name }} has its longest length of {{ worm.longestLength.length }}cm at  {{ timestamp2Date(worm.longestLength.timestamp) }} 
  </div>
</div>

答案 2 :(得分:0)

希望这与您的想法很接近。

如果我正确理解,您的意图是在每个蠕虫上创建名为longestLengthtimestampForLongest的“计算属性”(或某种类型的吸气剂)。这些将基于measurements中的state得出其值。

我试图通过使每个蠕虫成为Vue实例来做到这一点。显然,Vue实例提供了许多其他功能,例如渲染,在这种情况下是不需要的。在Vue 2中,没有任何方法可以仅挑出所需的位。有传言说Vue 3在这方面可能更具模块化。我们唯一需要的位是可观察的data(可以使用Vue.observable来实现)和计算的属性(只能通过Vue实例使用)。就其价值而言,这就是Vuex在后台工作,创建单独的Vue实例并插入数据,计算等的方式。

虽然下面的代码看起来很长,但其中大部分与生成合适的测试数据有关。我最初生成的数据带有嵌套在蠕虫中的度量,然后将其提取为您在突变中指定的格式。 worms中的每个实例在添加到state之前都会转换为Vue实例。

我在特别重要的部分中添加了// This bit is important条注释,以使其更容易被忽略。

// This bit is important
const Worm = Vue.extend({
  computed: {
    longestLength () {
      let longest = null

      for (const id of this.measurements) {
        const measurement = store.state.measurements[id]

        if (!longest || measurement.length > longest.length) {
          longest = measurement
        }
      }

      return longest
    },

    timestampForLongest () {
      return this.longestLength.timestamp
    }
  }
})


const state = {
  worms: {},
  measurements: {}
};

const mutations = {
  populate (state, worms) {
    const wormState = {}
    const measurementsState = {}
    let measurementId = 0

    for (const worm of worms) {
      const measurementIds = []

      for (const measurement of worm.measurements) {
        measurementId++
        measurementIds.push(measurementId)
        measurementsState[measurementId] = {id: measurementId, ...measurement}
      }

      // This bit is important
      wormState[worm.id] = new Worm({
        data: {...worm, measurements: measurementIds}
      })
    }

    state.worms = wormState
    state.measurements = measurementsState
  }
};

const getters = {
  // This bit is important
  sortedWorms (state) {
    return Object.values(state.worms).sort((wormA, wormB) => wormA.timestampForLongest - wormB.timestampForLongest)
  }
};

const actions = {
  populateWorms ({commit}) {
    const worms = []

    for (let wIndex = 0; wIndex < 800; ++wIndex) {
      const measurements = []

      for (let mIndex = 0; mIndex < 3; ++mIndex) {
        measurements.push({
          length: Math.round(Math.random() * 100) / 10,
          timestamp: Math.round(Math.random() * 1e6)
        })
      }

      worms.push({
        measurements,
        name: 'Worm ' + wIndex,
        id: wIndex
      })
    }

    commit('populate', worms)
  }
}

const store = new Vuex.Store({
  state,
  mutations,
  getters,
  actions
})

new Vue({
  el: '#app',
  store,

  computed: {
    sortedWorms () {
      return this.$store.getters.sortedWorms
    }
  },

  methods: {
    go () {
      this.$store.dispatch('populateWorms')
    }
  }
})
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vuex@3.1.1/dist/vuex.js"></script>
<div id="app">
  <button @click="go">Go</button>
  <div v-for="worm in sortedWorms">
    {{ worm.name }} - {{ worm.longestLength }}
  </div>
</div>

考虑到您对最佳排序的基本要求,我不确定,这是否是实现所有这些的好方法。但是,这似乎与您在每种蠕虫上实现计算属性的意图差不多。

答案 3 :(得分:0)

我建议您采用完全不同的方法: 1.避免以任何方式分类 2.相反,在每个蠕虫对象上都具有与max_length和max_time相对应的属性,并在发布(或记录)该蠕虫的新观测值时更新它们(max属性)

这样,您可以避免每次都进行排序。

答案 4 :(得分:0)

通常,当我用大量数据制作Vue应用程序时,我会做这样的事情:

const vm = new Vue({
  data() {
    return {
      worms: [
        {id: 1,name: "Slurms McKenzie",measurements: [1, 6, 9]},
        {id: 2,name: "Memory worm",measurements: [3, 4, 12]},
        {id: 3,name: "Alaskan Bull Worm",measurements: [5, 7, 14]}
      ],

      measurements: [
        {id: 1,length: 5.2,timestamp: 123},
        {id: 2,length: 3.4,timestamp: 456},
        {id: 3,length: 5.4,timestamp: 789}
      ]
    };
  },

  computed: {
    sortedByLength() {
      return [...this.measurements]
        .sort((a, b) => a.length - b.length)
        .map(measurement => measurement.id)
        .map(id => this.worms.find(worm => worm.id === id));
    },

    timestampForLongest() {
      return this.sortedByLength[0].timestamp;
    }
  }
});

Vue将更新更改的计算属性,否则将其缓存。您需要做的就是将其转换为Vuex的状态/获取器,并且原理相同。

将它们存储为数组比存储为对象要容易得多。如果必须使用对象,则可以使用lodash库来帮助您进行排序,而又不会令人讨厌。