ko.js:将数据添加到现有虚拟机

时间:2017-05-22 11:15:27

标签: javascript object knockout.js

假设以下情况:

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 observable中,以便稍后使用(例如删除等)。

我不确定是否循环使用喜欢并过滤原始数据是一种好方法。关于这个主题是否有最佳实践?

1 个答案:

答案 0 :(得分:1)

我首先要实现一种方法来合并likesentries的列表。如果没有性能优化或架构影响,这可能是:

  • 映射到您的entries
  • liked属性添加到从服务器收到的现有对象
  • 通过循环true
  • 确定其值(falselikes

在下面的代码段中,您会看到这个实现很难阅读,并且一旦您的列表增长就会变慢O(n*m)我相信......但不要引用我的话)< / em>的

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存储所有liked 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>

现在,要解决的最后一个问题是您是否要重新使用视图模型。目前,每次更新时都会完全替换您的项目数组。如果淘汰赛必须呈现列表或进行其他计算,那么增加一些复杂性以获得性能可能是值得的...例如:

  • 创建一个Record视图模型,可以使用identry进行实例化,是否为liked
  • 收到新数据时,请查看现有的viewmodel
    • 如果有:更新其liked状态
    • 如果没有视图模型:创建一个新模型并将其添加到您的集合中

编辑:为了好玩,我也把它作为一个例子:

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>