我正在寻找通过关系优化归一化对象排序的方法。让我们假设一个应用程序需要排序 我有一个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
(或最终被称为的东西),但是我有一些挑战。
measurement.__ob__.dep.depend()
之类的吸气剂,但是我并没有为此而烦恼。$store
)保留在吸气剂范围内。我可能可以使用箭头功能,所以我对此并不担心。我可以使用Vue在普通javascript对象中按需计算和缓存值吗?
答案 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
,其中包含worms
和measurements
列表。每个worm
都有一个度量指标列表,我认为它与measurements
列表相关。
现在state
应该在Vuex store
的内部定义。现在,您的store
将包含四个主要元素,包括:状态,获取器,操作和突变。
因此,state
本质上可以视为整个应用程序的单一事实来源。 但是我们的组件和路由如何访问存储在状态中的数据?那么,getters
会将数据从store
返回到我们的组件,在这种情况下,我们想要获取sortedByTSDec
和sortedByTSAsc
方法。
因此,现在您已经了解了如何从state
获取数据,让我们看看如何设置数据到state
中。您必须认为我可以定义设置器,对吧?好吧,不,Vuex “设置器” 的命名略有不同。您应该定义一个mutation
才能将数据设置到state
中。
最后,actions
与mutations
类似,但是它们没有直接突变state
,而是提交了一个突变。 感到困惑吗?只需考虑一下actions
,例如异步功能,而mutations
是同步。
在此示例中,我不知道蠕虫数据的生成位置,它可能来自其他服务器,数据库等等。因此,generateData
操作将请求并等待数据,并且在数据准备好后将调用populate
变异来填充state
。
Worn
类呢?这是魔术发生的地方。 Vue.extend()
方法创建基本Vue构造函数的 子类 。 但是为什么? ?因为此子类具有data
选项。当我们使用生成的磨损数据在populate
突变中设置此值时。换句话说,state.worms
包含Worn
对象的列表。
另外,我们声明computed
属性,以使用实例数据计算longestLength
和timestampForLongest
。
现在,如果要按最长长度的时间戳对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)
希望这与您的想法很接近。
如果我正确理解,您的意图是在每个蠕虫上创建名为longestLength
和timestampForLongest
的“计算属性”(或某种类型的吸气剂)。这些将基于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库来帮助您进行排序,而又不会令人讨厌。