【问题标题】:How to update element inside List with ImmutableJS?如何使用 ImmutableJS 更新 List 中的元素?
【发布时间】:2015-04-12 13:01:51
【问题描述】:

这是官方文档所说的

updateIn(keyPath: Array<any>, updater: (value: any) => any): List<T>
updateIn(keyPath: Array<any>, notSetValue: any, updater: (value: any) => any): List<T>
updateIn(keyPath: Iterable<any, any>, updater: (value: any) => any): List<T>
updateIn(keyPath: Iterable<any, any>, notSetValue: any, updater: (value: any) => any): List<T>

普通的 Web 开发人员(不是函数式程序员)无法理解这一点!

我有一个非常简单的(用于非功能性方法)案例。

var arr = [];
arr.push({id: 1, name: "first", count: 2});
arr.push({id: 2, name: "second", count: 1});
arr.push({id: 3, name: "third", count: 2});
arr.push({id: 4, name: "fourth", count: 1});
var list = Immutable.List.of(arr);

如何更新list,其中名称为 third 的元素的 count 设置为 4

【问题讨论】:

  • 我不知道它长什么样,但是文档很糟糕facebook.github.io/immutable-js/docs/#/List/update
  • 严重赞成对文档的挖掘。如果该语法的目标是让我感到愚蠢/不足,那么肯定是成功的。但是,如果目标是传达 Immutable 的工作原理,那么……
  • 我必须同意,我发现 immutable.js 的文档非常令人沮丧。公认的解决方案使用我在文档中根本没有看到的方法 findIndex()。
  • 我宁愿他们给每个函数一个例子而不是那些东西。
  • 同意。文档一定是由某个科学家在泡沫中编写的,而不是由想要展示一些简单示例的人编写的。文档需要一些文档。例如,我喜欢下划线的文档,更容易看到实际示例。

标签: javascript functional-programming immutable.js


【解决方案1】:

最合适的情况是同时使用findIndexupdate 方法。

list = list.update(
  list.findIndex(function(item) { 
    return item.get("name") === "third"; 
  }), function(item) {
    return item.set("count", 4);
  }
); 

附:并非总是可以使用地图。例如。如果名称不是唯一的,并且我想更新所有具有相同名称的项目。

【讨论】:

  • 如果您需要重复名称,请使用多映射 - 或以元组作为值的映射。
  • 如果没有以“三”为名称的元素,这不会不小心更新列表的最后一个元素吗?在这种情况下 findIndex() 将返回 -1,update() 将解释为最后一个元素的索引。
  • 不,-1 不是最后一个元素
  • @Sam Storie 是对的。来自文档:“索引可能是一个负数,它从列表末尾返回索引。v.update(-1) 更新列表中的最后一项。” facebook.github.io/immutable-js/docs/#/List/update
  • 我不能调用 item.set,因为对我来说 item 不是 Immutable 类型,我必须先映射 item,调用 set,然后再将其转换回 object。所以对于这个例子,function(item) { return Map(item).set("count", 4).toObject(); }
【解决方案2】:

使用.setIn(),您也可以这样做:

let obj = fromJS({
  elem: [
    {id: 1, name: "first", count: 2},
    {id: 2, name: "second", count: 1},
    {id: 3, name: "third", count: 2},
    {id: 4, name: "fourth", count: 1}
  ]
});

obj = obj.setIn(['elem', 3, 'count'], 4);

如果我们不知道要更新的条目的索引。使用.findIndex() 很容易找到它:

const indexOfListToUpdate = obj.get('elem').findIndex(listItem => {
  return listItem.get('name') === 'third';
});
obj = obj.setIn(['elem', indexOfListingToUpdate, 'count'], 4);

希望对你有帮助!

【讨论】:

  • 你在fromJS( ) 中缺少{ },没有大括号它不是有效的JS。
  • 我不会将返回的对象称为“arr”,因为它是一个对象。只有 arr.elem 是一个数组
【解决方案3】:
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)

说明

更新 Immutable.js 集合总是返回这些集合的新版本,而原始版本保持不变。因此,我们不能使用 JavaScript 的 list[2].count = 4 变异语法。相反,我们需要调用方法,就像我们对 Java 集合类所做的那样。

让我们从一个更简单的例子开始:只是列表中的计数。

var arr = [];
arr.push(2);
arr.push(1);
arr.push(2);
arr.push(1);
var counts = Immutable.List.of(arr);

现在,如果我们想更新第三个项目,一个普通的 JS 数组可能看起来像:counts[2] = 4。由于我们不能使用突变,并且需要调用方法,因此我们可以使用:counts.set(2, 4) - 这意味着在索引2 处设置值4

深度更新

你给出的例子有嵌套数据。我们不能只在初始集合上使用set()

Immutable.js 集合具有一系列名称以“In”结尾的方法,可让您在嵌套集合中进行更深入的更改。大多数常见的更新方法都有一个相关的“In”方法。例如setsetIn。这些“In”方法不接受索引或键作为第一个参数,而是接受“键路径”。键路径是一组索引或键,说明如何获取您希望更新的值。

在您的示例中,您希望更新列表中索引 2 处的项目,然后更新该项目中键“count”处的值。所以关键路径是[2, "count"]setIn 方法的第二个参数和set 一样,是我们要放在那里的新值,所以:

list = list.setIn([2, "count"], 4)

找到正确的关键路径

更进一步,您实际上说过要更新 名称为“三”的项目,这与第三个项目不同。例如,您的列表可能没有排序,或者名称为“two”的项目之前已被删除?这意味着首先我们需要确保我们确实知道正确的密钥路径!为此,我们可以使用findIndex() 方法(顺便说一下,它的工作方式几乎与Array#findIndex 完全相同)。

一旦我们在列表中找到了包含我们要更新的项目的索引,我们就可以提供我们要更新的值的键路径:

var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)

注意:SetUpdate

最初的问题提到了更新方法而不是设置方法。我将解释该函数中的第二个参数(称为updater),因为它与set() 不同。 set() 的第二个参数是我们想要的新值,update() 的第二个参数是一个函数,它接受之前的值并返回我们想要的新值。然后,updateIn()update() 的“In”变体,它接受一个关键路径。

举例来说,我们想要您的示例的变体,它不仅将计数设置为 4,而是递增现有计数,我们可以提供一个函数,将一个添加到现有值:

var index = list.findIndex(item => item.name === "three")
list = list.updateIn([index, "count"], value => value + 1)

【讨论】:

    【解决方案4】:

    这是官方文档所说的……updateIn

    您不需要updateIn,它仅适用于嵌套结构。您正在寻找update method,它的签名和文档要简单得多:

    返回一个新列表,其索引处的值已更新,并返回 使用现有值调用更新程序的值,或者 notSetValue 如果 未设置索引。

    update(index: number, updater: (value: T) => T): List<T>
    update(index: number, notSetValue: T, updater: (value: T) => T): List<T>
    

    正如Map::update docs 所建议的那样,“相当于:list.set(index, updater(list.get(index, notSetValue)))”。

    名称为“第三”的元素

    列表不是这样工作的。您必须知道要更新的元素的索引,否则您必须搜索它。

    如何更新名称为 third 的元素的计数设置为 4 的列表?

    应该这样做:

    list = list.update(2, function(v) {
        return {id: v.id, name: v.name, count: 4};
    });
    

    【讨论】:

    • 不,您选择的数据结构不满足业务逻辑 :-) 如果您想按名称访问元素,您应该使用映射而不是列表。
    • @bergi,真的吗?因此,如果我有一个班级学生列表,并且我想对这个学生数据进行 CRUD 操作,那么您会建议使用 Map 而不是 List 吗?或者你只是说因为OP说“按名称选择项目”而不是“按ID选择项目”?
    • @DavidGilbertson:CRUD?我建议使用数据库 :-) 但是是的,id->student map 听起来比列表更合适,除非您对它们有订单并想按索引选择它们。
    • @bergi 我想我们会同意不同意,我从 API 以带有 ID 的事物数组的形式返回大量数据,我需要通过 ID 更新这些事物。这是一个 JS 数组,因此在我看来应该是一个 immutableJS 列表。
    • @DavidGilbertson:我认为这些 API 采用 JSON 数组作为集合——它们是明确无序的。
    【解决方案5】:

    使用.map()

    list = list.map(item => 
       item.get("name") === "third" ? item.set("count", 4) : item
    );
    

    var arr = [];
    arr.push({id: 1, name: "first", count: 2});
    arr.push({id: 2, name: "second", count: 1});
    arr.push({id: 3, name: "third", count: 2});
    arr.push({id: 4, name: "fourth", count: 1});
    var list = Immutable.fromJS(arr);
    
    var newList = list.map(function(item) {
        if(item.get("name") === "third") {
          return item.set("count", 4);
        } else {
          return item;
        }
    });
    
    console.log('newList', newList.toJS());
    
    // More succinctly, using ES2015:
    var newList2 = list.map(item => 
        item.get("name") === "third" ? item.set("count", 4) : item
    );
    
    console.log('newList2', newList2.toJS());
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"&gt;&lt;/script&gt;

    【讨论】:

    • 谢谢你 - 这里有这么多可怕的令人费解的答案,真正的答案是“如果你想更改列表,请使用地图”。这就是持久不可变数据结构的全部意义所在,您不需要“更新”它们,而是创建一个包含更新值的新结构;而且这样做很便宜,因为未修改的列表元素是共享的。
    【解决方案6】:

    我真的很喜欢thomastuts 网站上的这种方法:

    const book = fromJS({
      title: 'Harry Potter & The Goblet of Fire',
      isbn: '0439139600',
      series: 'Harry Potter',
      author: {
        firstName: 'J.K.',
        lastName: 'Rowling'
      },
      genres: [
        'Crime',
        'Fiction',
        'Adventure',
      ],
      storeListings: [
        {storeId: 'amazon', price: 7.95},
        {storeId: 'barnesnoble', price: 7.95},
        {storeId: 'biblio', price: 4.99},
        {storeId: 'bookdepository', price: 11.88},
      ]
    });
    
    const indexOfListingToUpdate = book.get('storeListings').findIndex(listing => {
      return listing.get('storeId') === 'amazon';
    });
    
    const updatedBookState = book.setIn(['storeListings', indexOfListingToUpdate, 'price'], 6.80);
    
    return state.set('book', updatedBookState);
    

    【讨论】:

      【解决方案7】:

      你可以使用map:

      list = list.map((item) => { 
          return item.get("name") === "third" ? item.set("count", 4) : item; 
      });
      

      但这会遍历整个集合。

      【讨论】:

        猜你喜欢
        • 2023-04-02
        • 2016-04-26
        • 2016-06-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多