javascript - ko.js : Add data to existing vm

标签 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 中,以便稍后使用(例如删除喜欢的内容)。

我不确定循环点赞并过滤原始数据是否是一个好方法。有关于这个主题的最佳实践吗?

最佳答案

我首先会实现一种将喜欢列表和条目组合起来的方法。如果没有性能优化或架构影响,这可能是这样的:

  • 映射您的条目
  • 向从服务器接收的现有对象添加 liked 属性
  • 通过循环您的点赞来确定其值(truefalse)

在下面的代码片段中,您将看到这个实现,它非常难以阅读,并且一旦您的列表增长就会变慢(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 模型,可以使用 identry 以及它是否喜欢进行实例化
  • 收到新数据时,查找现有的 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/

相关文章:

javascript - 为什么 toString() 是一个不需要对象的方法?

javascript - 订阅页面加载时的值

javascript - Knockout JS 清除焦点输入

javascript - onkeyup 激活很多ajax请求

用 Java 解析 JavaScript

javascript - 异步调用 axios API 后使用 React hook 和 redux 设置状态

javascript - Knockout - 访问对象数组中的属性

javascript - Grunt、Require、Node、OMG 我从哪里开始

javascript - 寻找有关 JS Object.constructor() (方法)的文档

java - 通过 JMS 接收 Java 对象