Как обновить элемент внутри списка с помощью ImmutableJS?

Вот что сказали официальные документы

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

Как обновить list, где элемент с именем третий имеет значение count 4?

Ответ 1

Наиболее подходящим случаем является использование методов findIndex и update.

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

P.S. Не всегда можно использовать Карты. Например. если имена не уникальны, и я хочу обновить все элементы с одинаковыми именами.

Ответ 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);

Надеюсь, поможет!

Ответ 3

Вот что сказали официальные документы... updateIn

Вам не нужен updateIn, который предназначен только для вложенных структур. Вы ищете метод update, который имеет гораздо более простую подпись и документацию:

Возвращает новый список с обновленным значением в индексе с возвратом значение вызывающего обновления с существующим значением или notSetValue if индекс не был установлен.

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

Ответ 4

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) - это значит установить значение 4 в индекс 2.

Глубокие обновления

Приведенный вами пример имеет вложенные данные. Мы не можем просто использовать set() в исходной коллекции.

В коллекциях Immutable.js есть семейство методов с именами, заканчивающимися на "In", которые позволяют вам делать более глубокие изменения во вложенном наборе. Наиболее распространенные методы обновления имеют связанный метод "В". Например, для set существует setIn. Вместо того, чтобы принимать индекс или ключ в качестве первого аргумента, эти методы "В" принимают "ключевой путь". Ключевым путем является массив индексов или ключей, который иллюстрирует, как получить значение, которое вы хотите обновить.

В вашем примере вы хотели обновить элемент в списке по индексу 2, а затем значение в ключе "счет" внутри этого элемента. Таким образом, путь ключа будет [2, "count"]. Второй параметр метода setIn работает так же, как set, это новое значение, которое мы хотим поместить туда, поэтому:

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

Поиск правильного пути ключа

Идя на один шаг дальше, вы на самом деле сказали, что хотите обновить элемент, где имя "три" отличается от третьего. Например, возможно, ваш список не отсортирован, или, возможно, элемент с именем "два" был удален ранее? Это означает, что сначала нам нужно убедиться, что мы действительно знаем правильный путь ключа! Для этого мы можем использовать метод 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() является вариацией "In" update(), которая принимает ключевой путь.

Скажем, например, мы хотели вариант вашего примера, который не просто установил счетчик 4, а вместо этого увеличил существующий счетчик, мы могли бы предоставить функцию, которая добавляет ее к существующему значению:

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

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

Ответ 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; 
});

Но это будет итерация по всей коллекции.