Як оновити елемент всередині списку за допомогою 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
Я повинен погодитись, я вважаю, що документація на Imtable.js серйозно засмучує. Прийняте рішення для цього використовує метод findIndex (), якого я навіть не бачу в документах.
RichardForrester

3
Я вважаю за краще, щоб вони наводили приклад для кожної функції замість цих речей.
RedGiant

4
Домовились. Документацію, мабуть, написав якийсь учений міхуром, а не хтось, хто хоче показати кілька простих прикладів. Документація потребує певної документації. Наприклад, мені подобається документація підкреслення, набагато простіше бачити практичні приклади.
ReduxDJ

Відповіді:


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
@Sam Storie має рацію. З документів: "індекс може бути від'ємним числом, яке індексує з кінця Списку. V.update (-1) оновлює останній елемент у Списку." facebook.github.io/immutable-js/docs/#/List/update
Габріель Ферраз

1
Я не міг зателефонувати item.set, тому що для мене елемент не був типом Immutable, я повинен був спочатку зіставити елемент, встановити виклик, а потім знову перетворити його на об'єкт Отже, для цього прикладу функція (item) {повернути 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 - це масив
Нір О.

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 мають сімейство методів з іменами, що закінчуються на "В", які дозволяють вносити більш глибокі зміни в вкладений набір. Найбільш поширені методи оновлення мають відповідний метод "В". Наприклад для setє setIn. Замість того, щоб прийняти індекс або ключ як перший аргумент, ці методи "В" приймають "шлях ключа". Ключовий шлях - це масив індексів або ключів, який ілюструє, як дійти до значення, яке ви хочете оновити.

У вашому прикладі ви хотіли оновити елемент у списку в індексі 2, а потім значення за ключем "рахувати" всередині цього елемента. Отже, ключовим шляхом був би [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такий підхід ви пропонуєте? Або ви це говорите лише тому, що ОП сказав "вибрати елемент по імені", а не "вибрати елемент за ідентифікатором"?
Девід Гілбертсон

2
@DavidGilbertson: CRUD? Я б запропонував базу даних :-) Але так, ідентифікатор-> студентська карта звучить більш доречно, ніж список, якщо ви не отримали замовлення на них і хочете вибрати їх за індексом.
Бергі

1
@bergi Я думаю, що ми погоджуємося не погоджуватися, я отримую багато даних назад у API у вигляді масивів речей з ідентифікаторами, де мені потрібно оновити ці речі за ідентифікатором. Це масив JS, і, на мій погляд, тому повинен бути незмінний список JS.
Девід Гілбертсон

1
@DavidGilbertson: Я думаю, що ці API приймають масиви JSON для наборів - явно не упорядкованих.
Бергі

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
Дякую за це - стільки жахливих суперечливих відповідей тут, справжня відповідь - "якщо ви хочете змінити список, використовуйте карту". У цьому вся суть стійких незмінних структур даних, ви їх не "оновлюєте", ви створюєте нову структуру з оновленими значеннями в ній; і це дешево, тому що немодифіковані елементи списку є спільними.
Корній

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

Але це повторить всю колекцію.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.