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

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>

Нормальный веб-разработчик (не функциональный программист) не может этого понять!

У меня довольно простой (для нефункционального подхода) случай.

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
Виталий Корсаков
71
Серьезная поддержка за раскопки в документации. Если цель этого синтаксиса - заставить меня чувствовать себя тупым / неадекватным, определенно успех. Однако если цель - показать, как работает Immutable, ну ...
Оуэн,
9
Я должен согласиться, я нахожу документацию для immutable.js серьезно разочаровывающей. В принятом решении для этого используется метод findIndex (), который я вообще не вижу в документации.
RichardForrester
3
Я бы предпочел, чтобы они приводили примеры для каждой функции вместо этих вещей.
RedGiant 01
4
Согласовано. Документация, должно быть, была написана каким-то ученым в пузыре, а не кем-то, кто хочет показать несколько простых примеров. Для документации нужна некоторая документация. Например, мне нравится документация с подчеркиванием, гораздо легче увидеть практические примеры.
ReduxDJ 02

Ответы:

119

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

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

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

Виталий Корсаков
источник
1
Если вам нужны повторяющиеся имена, используйте множественные карты или карты с кортежами в качестве значений.
Берги
5
Не приведет ли это к непреднамеренному обновлению последнего элемента в списке, если нет элемента с «тройкой» в качестве имени? В этом случае findIndex () вернет -1, что update () интерпретирует как индекс последнего элемента.
Сэм Стори
1
Нет, -1 - это не последний элемент
Виталий Корсаков
9
@ Сэм Стори права. Из документов: «index может быть отрицательным числом, которое индексирует обратно с конца списка. V.update (-1) обновляет последний элемент в списке». facebook.github.io/immutable-js/docs/#/List/update
Габриэль Ферраз,
1
Я не мог вызвать item.set, потому что для меня элемент не был неизменяемым типом, мне пришлось сначала сопоставить элемент, вызвать набор, а затем снова преобразовать его обратно в объект. Итак, для этого примера function (item) {return Map (item) .set ("count", 4) .toObject (); }
syclee 08
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 - это массив
Нир О.
21
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)

объяснение

Обновление коллекций Immutable.js всегда возвращает новые версии этих коллекций, оставляя исходную неизменной. Из-за этого мы не можем использовать list[2].count = 4синтаксис мутации JavaScript . Вместо этого нам нужно вызывать методы, как и в случае с классами коллекций Java.

Начнем с более простого примера: просто счетчики в списке.

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

Теперь , если мы хотим , чтобы обновить 3 - й пункт, простой массив JS может выглядеть следующим образом : counts[2] = 4. Поскольку мы не можем использовать мутацию и должны вызвать метод, вместо этого мы можем использовать: counts.set(2, 4)- это означает установить значение 4в индексе 2.

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

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

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

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

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

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

Сделав еще один шаг, вы на самом деле сказали, что хотите обновить элемент, имя которого - «три», что отличается от просто 3-го элемента. Например, может быть, ваш список не отсортирован, или, возможно, элемент с именем «два» был удален ранее? Это означает, что сначала нам нужно убедиться, что мы действительно знаем правильный путь ключа! Для этого мы можем использовать findIndex()метод (который, кстати, работает почти так же, как Array # findIndex ).

Как только мы нашли индекс в списке, в котором есть элемент, который мы хотим обновить, мы можем предоставить ключевой путь к значению, которое мы хотим обновить:

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

NB: SetvsUpdate

В исходном вопросе упоминаются методы обновления, а не установленные методы. Я объясню второй аргумент этой функции (называемой updater), поскольку он отличается от set(). В то время как второй аргумент set()- это новое значение, которое мы хотим, второй аргумент update()- это функция, которая принимает предыдущее значение и возвращает новое значение, которое мы хотим. Тогда updateIn()это вариант «In», update()который принимает ключевой путь.

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

var index = list.findIndex(item => item.name === "three")
list = list.updateIn([index, "count"], value => value + 1)
Ли Байрон
источник
19

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

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

Возвращает новый список с обновленным значением по индексу с возвращаемым значением вызывающего средства обновления с существующим значением или 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))) ".

где элемент с именем "третий"

Списки работают не так. Вы должны знать индекс элемента, который хотите обновить, или вам нужно искать его.

Как я могу обновить список, в котором для элемента с третьим именем установлено значение 4?

Это должно сделать это:

list = list.update(2, function(v) {
    return {id: v.id, name: v.name, count: 4};
});
Берги
источник
14
Нет, ваш выбор структуры данных не соответствует бизнес-логике :-) Если вы хотите получить доступ к элементам по их имени, вам следует использовать карту вместо списка.
Берги
1
@bergi, правда? Итак, если у меня есть, скажем, список учащихся в классе, и я хочу выполнять операции CRUD с этими данными учащихся, то Mapкакой Listподход вы бы предложили? Или вы говорите это только потому, что OP сказал «выбрать элемент по имени», а не «выбрать элемент по идентификатору»?
Дэвид Гилбертсон
2
@DavidGilbertson: CRUD? Я бы посоветовал базу данных :-) Но да, карта id-> student звучит более уместно, чем список, если только у вас нет на них порядка и вы не хотите выбирать их по индексу.
Bergi
1
@bergi Я думаю, мы согласимся не согласиться, я получаю много данных обратно от API в виде массивов вещей с идентификаторами, где мне нужно обновить эти вещи по идентификатору. Это массив 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>

Meistro
источник
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);
Драйден Уильямс
источник
-2

Вы можете использовать map:

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

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

Альдо Поркалло
источник