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


129

这是官方文件所说的

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名称为第三的元素的计数设置为4


43
我不知道它的样子,但是文档太糟糕了facebook.github.io/immutable-js/docs/#/List/update
Vitalii Korsakov

71
对文档的挖掘工作进行了认真的支持。如果该语法的目标是让我感到愚蠢/不足,绝对成功。但是,如果目标是传达不可变的工作原理,那么……
Owen

9
我必须同意,我发现immutable.js的文档非常令人沮丧。公认的解决方案使用了findIndex()方法,我什至在文档中都没有看到它。
RichardForrester '16

3
我宁愿他们为每个函数提供一个示例,而不是为那些事情提供示例。
RedGiant

4
同意 该文档一定是由某个科学家在泡沫中编写的,而不是某个想要显示一些简单示例的人编写的。该文档需要一些文档。例如,我喜欢下划线文档,更容易看到实际示例。
ReduxDJ

Answers:


119

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

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

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


1
如果您需要重复的名称,请使用多图-或以元组为值的图。
Bergi 2015年

5
如果没有名称以“三”表示的元素,这是否会无意中更新列表的最后一个元素?在那种情况下,findIndex()将返回-1,而update()将被解释为最后一个元素的索引。
山姆·斯托伊,2015年

1
不,-1不是最后一个元素
Vitalii Korsakov 2015年

9
@Sam Storie是正确的。从文档中:“索引可能是负数,它从列表的末尾开始索引。v.update(-1)更新列表中的最后一项。” facebook.github.io/immutable-js/docs/#/列表/更新
Gabriel Ferraz

1
我无法调用item.set,因为对我而言,item不是不可变类型,我必须先映射该项目,调用set,然后再次将其转换回对象。因此,在此示例中,function(item){return Map(item).set(“ count”,4).toObject(); }
syclee

36

使用.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);

希望能帮助到你!


1
您缺少{ }内部fromJS( ),没有花括号,它不是有效的JS。
安迪

1
我不会将返回的对象称为“ arr”,因为它是一个对象。仅arr.elem是一个数组
尼尔O.

21
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。由于我们不能使用mutation,而需要调用一个方法,因此我们可以使用:counts.set(2, 4)-表示4在index处设置值2

深度更新

您给出的示例虽然具有嵌套数据。我们不能只使用set()初始集合。

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

在您的示例中,您想要更新列表中索引2处的项目,然后更新该项目中键“ count”处的值。因此,关键路径将是[2, "count"]。该setIn方法的第二个参数就像一样工作set,它是我们要放入的新值,因此:

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

找到正确的密钥路径

更进一步,您实际上说过您想更新名称为“ 3”的项目,该名称与第3个项目不同。例如,也许您的列表未排序,或者早些时候删除了名为“ two”的项目?这意味着首先我们需要确保我们确实知道正确的密钥路径!为此,我们可以使用findIndex()方法(顺便说一下,它的工作原理几乎与Array#findIndex一样)。

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

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

注意:SetvsUpdate

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

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

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

19

这是官方文档所说的… updateIn

您不需要updateIn,仅适用于嵌套结构。您正在寻找具有更简单的签名和文档的updatemethod

返回一个新列表,该列表的索引值为更新的值,其中调用更新程序的返回值为现有值;如果未设置索引,则为notSetValue。

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”的元素

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

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

应该这样做:

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

14
不,您选择的数据结构不符合业务逻辑:-)如果要按元素名称访问元素,则应使用映射而不是列表。
Bergi 2015年

1
@bergi,真的吗?所以,如果我有一个班上的学生列表,并且想对这个学生数据进行CRUD操作,则建议使用a Map上方List的方法吗?还是您只说那是因为OP说“按名称选择项目”而不是“按ID选择项目”?
大卫·吉尔伯森

2
@DavidGilbertson:CRUD?我建议使用一个数据库:-)但是,是的,一个id-> student map听起来比列表更合适,除非您已对它们进行了订购并希望通过索引选择它们。
Bergi 2015年

1
@bergi我想我们会不同意,我从API那里以ID的事物数组的形式获取了大量数据,我需要通过ID更新这些事物。这是一个JS数组,因此我认为应该是一个不可变的JS列表。
大卫·吉尔伯森

1
@DavidGilbertson:我认为这些API采用JSON数组作为数组-这些数组显然是无序的。
Bergi

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
感谢您的回答-这里有这么多令人费解的答案,真正的答案是“如果您要更改列表,请使用地图”。这就是持久不变数据结构的全部要点,您无需“更新”它们,而是创建一个包含更新值的新结构;这样做很便宜,因为未修改的列表元素是共享的。
科尼(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);

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.