假设以下情况:
self.watchedRecords = ko.observableArray();
//Get watchlist entries from server
dataService.getWatched().then(function (resolve) {
//Push all items to the observable array
self.watchedRecords(resolve.entries);
});
在我的resolve
中,我有另一个键likes
,其中包含有关登录用户喜欢哪些条目的信息(当然是一个数组):
{
watchedItemGuid: 572, //user liked item in which the guid is 572
id: 3 //the like is saved in another table as item with the id 3
}
我尝试将有关喜欢或不喜欢的信息添加到可观察的 watchedRecords
中,以便稍后使用(例如删除喜欢的内容)。
我不确定循环点赞并过滤原始数据是否是一个好方法。有关于这个主题的最佳实践吗?
最佳答案
我首先会实现一种将喜欢
列表和条目
组合起来的方法。如果没有性能优化或架构影响,这可能是这样的:
- 映射您的
条目
- 向从服务器接收的现有对象添加
liked
属性 - 通过循环您的
点赞
来确定其值(true
或false
)
在下面的代码片段中,您将看到这个实现,它非常难以阅读,并且一旦您的列表增长就会变慢(O(n*m)
我相信......但是不要不要引用我的话)
var watchedRecords = ko.observableArray([]);
var setWatchedRecords = function(resolve) {
watchedRecords(
// Map over the entries, this is our base set of data
resolve.entries.map(
function(record) {
// Add one property toeach entry: "liked"
return Object.assign(record, {
// The value is liked is determined by our array
// of liked items.
liked: resolve.likes.some(function(like) {
// If some like contains our record id, we return true
return like.watchedItemId === record.id;
})
})
}
)
);
};
setWatchedRecords(getWatched());
console.log(watchedRecords().map(JSON.stringify));
// Mock data
function getEntries() {
return [ { id: 572, name: "Entry 2" }, { id: 573, name: "Entry 3" }, { id: 574, name: "Entry 4" }, { id: 575, name: "Entry 5" }, { id: 576, name: "Entry 6" } ]
};
function getLikes() {
return [ { watchedItemId: 572, id: 1 }, { watchedItemId: 576, id: 2 } ];
};
function getWatched() {
return { entries: getEntries(), likes: getLikes() };
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
我个人所做的初始优化(切换到 ES2015 语法):
- 使用
Set
存储所有喜欢的
ID(因此您只需循环访问它们一次) - 将您的功能分割成一些单一用途的功能。
var get = key => obj => obj[key];
var watchedRecords = ko.observableArray([]);
var getLikedIds = (resolve) => new Set(
resolve.likes.map(get("watchedItemId"))
);
var getWatchedRecords = (resolve) => {
var likes = getLikedIds(resolve);
return resolve.entries.map(
e => Object.assign(e, { liked: likes.has(e.id) })
);
};
watchedRecords(getWatchedRecords(getWatched()));
console.log(watchedRecords().map(JSON.stringify));
// Mock data
function getEntries() {
return [ { id: 572, name: "Entry 2" }, { id: 573, name: "Entry 3" }, { id: 574, name: "Entry 4" }, { id: 575, name: "Entry 5" }, { id: 576, name: "Entry 6" } ]
};
function getLikes() {
return [ { watchedItemId: 572, id: 1 }, { watchedItemId: 576, id: 2 } ];
};
function getWatched() {
return { entries: getEntries(), likes: getLikes() };
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
现在,要解决的最后一个问题是您是否要重用 View 模型。目前,您的元素数组在每次更新时都会被完全替换。如果 knockout 必须渲染列表或执行一些其他计算,那么增加一些复杂性以获得性能可能是值得的......例如:
- 创建一个
Record
View 模型,可以使用id
、entry
以及它是否喜欢
进行实例化 - 收到新数据时,查找现有的 View 模型
- 如果有:更新其
喜欢
状态 - 如果没有 View 模型:创建一个新 View 模型并将其添加到您的集合中
- 如果有:更新其
编辑:为了好玩,我也将其作为示例:
var Record = function(data, likeMap) {
Object.assign(this, data);
// Each `Record` instance has a reference to the
// manager's `Set` of liked ids. Now, we can
// compute whether we're a liked Record automatically!
this.liked = ko.pureComputed(
() => likeMap().has(this.id)
);
this.toString = () => "Id: " + this.id + ", liked: " + this.liked();
};
var RecordManager = function() {
const likes = ko.observable(new Set());
const recordMap = ko.observable(new Map());
// Our record viewmodels in a list
this.records = ko.pureComputed(
() => Array.from(recordMap().values())
).extend({ "deferred": true });
// This takes your server side likes and transforms
// it to a set of liked ids
const setLikes = likeData => {
likes(
new Set(likeData.map(get("watchedItemId")))
);
};
// This takes your server side records and transforms
// them to a Map of `id: Record`
const setRecords = recordData => {
recordMap(
recordData.reduce(
// Re-use our previously createdviewmodel if there is any
(map, r) => map.set(r.id, recordMap().get(r.id) || new Record(r, likes))
, new Map())
);
};
// Updating is now independent of order.
// Our Record instances contain a reference to the `likes` set,
// thereby automatically updating their like status
this.updateRecords = data => {
setRecords(data.entries);
setLikes(data.likes);
}
};
var rm = new RecordManager();
rm.updateRecords(getWatched());
console.log(rm.records().map(r => r.toString()));
// Mock data
function getEntries() {
return [ { id: 572, name: "Entry 2" }, { id: 573, name: "Entry 3" }, { id: 574, name: "Entry 4" }, { id: 575, name: "Entry 5" }, { id: 576, name: "Entry 6" } ]
};
function getLikes() {
return [ { watchedItemId: 572, id: 1 }, { watchedItemId: 576, id: 2 } ];
};
function getWatched() {
return { entries: getEntries(), likes: getLikes() };
};
// Utils
function get(key) { return function(obj) { return obj[key]; }; };
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
关于javascript - ko.js : Add data to existing vm,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44111468/