如何使用ImmutableJS更新列表中的元素?

135

这里是官方文档中的内容

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>

普通的网站开发者(非函数式编程员)无法理解这个!

对于非函数式方法来说,我的案例相当简单。

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);

如何更新具有名称为 third 的元素的计数设置为 4 的list


47
我不知道它看起来是什么样子,但文档很糟糕。http://facebook.github.io/immutable-js/docs/#/List/update - Vitalii Korsakov
75
对于那篇文档的批评,我给予了认真的点赞。如果文档的语法旨在让我感到愚蠢/不足,那么它绝对成功了。但是,如果其目的是传达Immutable的工作原理,那么...... - Owen
9
我必须承认,我觉得immutable.js的文档非常令人沮丧。目前接受的解决方案使用了一个findIndex()方法,但是我在文档中根本没有看到过这个方法。 - RichardForrester
4
我宁愿他们为每个函数举一个例子,而不是那些东西。 - RedGiant
5
同意。这份文档一定是由一个生活在自己的世界里的科学家编写的,而不是想要展示一些简单例子的人。这份文档需要一些文档。例如,我喜欢下划线文档,更容易看到实际的例子。 - ReduxDJ
显示剩余5条评论
7个回答

126

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

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

顺便说一句,有时候无法使用地图。例如,如果名称不唯一并且我想更新所有具有相同名称的项目。


1
如果您需要重复的名称,请使用multimaps - 或者将元组作为值的maps。 - Bergi
5
如果没有名称为“three”的元素,这样做是否会意外更新列表的最后一个元素?在这种情况下,findIndex()将返回-1,而update()将把它解释为最后一个元素的索引。 - Sam Storie
1
不,-1不是最后一个元素。 - Vitalii Korsakov
9
@Sam Storie是正确的。 根据文档:“索引可以是负数,从List的末尾进行索引。v.update(-1)会更新List中的最后一项。” https://facebook.github.io/immutable-js/docs/#/List/update - Gabriel Ferraz
1
我无法调用item.set方法,因为对于我来说,item不是Immutable类型,我必须先将其映射,然后调用set方法,最后再将其转换回对象。所以对于这个例子,function(item) { return Map(item).set("count", 4).toObject(); }应该这样写: - syclee
显示剩余2条评论

40

通过.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() 很容易找到它: .findIndex()
const indexOfListToUpdate = obj.get('elem').findIndex(listItem => {
  return listItem.get('name') === 'third';
});
obj = obj.setIn(['elem', indexOfListingToUpdate, 'count'], 4);

希望有所帮助!

1
你在 fromJS() 函数内缺少 {},没有这些大括号它就不是有效的 JS。 - Andy
1
我不会把返回的对象称为“arr”,因为它是一个对象。只有arr.elem是一个数组。 - Nir O.

24
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"方法。例如,对于set,有setIn。这些"In"方法不接受索引或键作为第一个参数,而是接受一个“键路径”。键路径是一个索引或键的数组,说明如何到达要更新的值。

在你的示例中,你想要更新列表中索引为2的项目,然后更新该项目内键为“count”的值。因此,键路径将是[2,“count”]setIn方法的第二个参数与set完全相同,它是我们要放置的新值,所以:

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

寻找正确的键路径

更进一步地,你实际上说你想要更新的项目是名称为"three"的项目,这与仅仅是第三个项目不同。例如,也许你的列表没有排序,或者之前删除了名为 "two" 的项?这意味着首先我们需要确保我们知道正确的键路径!为此,我们可以使用findIndex()方法(顺便说一下,它几乎与Array#findIndex完全相同)。

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

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

NB: Set vs Update

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

例如,假设我们想要一个你的示例的变体,不仅将计数设置为4,而且还将现有计数增加1,我们可以提供一个添加1到现有值的函数:

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

这个答案非常棒,是我见过的最好的immutableJS文档。 - Emma Earl Kent

19
这是官方文档中的内容...updateIn指仅用于嵌套结构。你需要的是update方法,它有更简单的签名和文档说明:不要使用updateIn,而要使用update方法。

Returns a new List with an updated value at index with the return value of calling updater with the existing value, or notSetValue if index was not set.

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

根据Map::update文档的建议,其等价于:list.set(index, updater(list.get(index, notSetValue)))

其中元素名称为“third”

这不是列表的工作方式。你必须知道要更新的元素的索引,或者必须搜索它。

如何更新其中一个元素名称为third且其计数设置为4的列表?

这应该可以做到:

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

14
不,你选择的数据结构不符合业务逻辑 :-) 如果你想通过名称访问元素,应该使用映射而不是列表。 - Bergi
1
@bergi,真的吗?所以如果我有一个班级学生名单,我想对这些学生数据进行CRUD操作,使用Map覆盖List是你建议的方法吗?还是你只是因为OP说“按名称选择项目”而不是“按ID选择项目”才这么说? - David Gilbertson
2
@DavidGilbertson:CRUD?我建议使用数据库 :-) 但是,除非您对它们有顺序并且想要通过索引选择它们,否则id->student映射比列表更合适。 - Bergi
1
@bergi 我想我们会同意各自的看法,我从API中以ID为形式获取到许多数据,需要通过ID更新这些数据。这是一个JS数组,在我看来,应该是一个immutableJS列表。 - David Gilbertson
1
@DavidGilbertson:我认为那些API接受JSON数组作为集合 - 这些集合明确是无序的。 - Bergi
显示剩余3条评论

12

使用.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());
<script src="https://cdnjs.cloudflare.com/ajax/libs/immutable/3.8.1/immutable.js"></script>


1
谢谢你的回答 - 这里有很多可怕而复杂的答案,真正的答案是“如果你想改变一个列表,请使用map”。这就是持久不变数据结构的全部意义,你不会“更新”它们,而是创建一个新的包含更新值的结构;而且这样做很便宜,因为未修改的列表元素是共享的。 - Korny

2

我非常喜欢来自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);

-2

你可以使用map

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

但这将遍历整个集合。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接