Jak zaktualizować element wewnątrz listy za pomocą ImmutableJS?

129

Oto, co powiedzieli oficjalni doktorzy

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>

Nie ma mowy, żeby zwykły twórca stron internetowych (nie funkcjonalny programista) to zrozumiał!

Mam dość prosty (dla niefunkcjonalnego podejścia) przypadek.

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

Jak mogę zaktualizować, listgdzie element o nazwie trzeciej ma liczbę ustawioną na 4 ?

Vitalii Korsakov
źródło
45
Nie wiem, jak to wygląda, ale dokumentacja jest okropna facebook.github.io/immutable-js/docs/#/List/update
Vitalii Korsakov
72
Poważne głosy za wykopaliskami w dokumentacji. Jeśli celem tej składni jest sprawienie, żebym poczuł się głupio / nieadekwatnie, definitywny sukces. Jeśli jednak celem jest przekazanie, jak działa Immutable, cóż ...
Owen,
9
Muszę się zgodzić, uważam, że dokumentacja immutable.js jest bardzo frustrująca. Zaakceptowane rozwiązanie tego problemu wykorzystuje metodę findIndex (), której w ogóle nie widzę w dokumentacji.
RichardForrester,
3
Wolałbym, żeby zamiast tych rzeczy podawali przykład dla każdej funkcji.
RedGiant
4
Zgoda. Dokumentacja musiała zostać napisana przez jakiegoś naukowca w bańce, a nie przez kogoś, kto chce pokazać proste przykłady. Dokumentacja wymaga dokumentacji. Na przykład podoba mi się dokumentacja z podkreśleniami, dużo łatwiejsze do zobaczenia praktyczne przykłady.
ReduxDJ

Odpowiedzi:

119

Najbardziej odpowiednim przypadkiem jest użycie obu metod findIndexi update.

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

PS Nie zawsze można korzystać z Map. Np. Jeśli nazwy nie są unikalne i chcę zaktualizować wszystkie elementy o tych samych nazwach.

Vitalii Korsakov
źródło
1
Jeśli potrzebujesz zduplikowanych nazw, użyj multimap - lub map z krotkami jako wartościami.
Bergi
5
Czy nie spowoduje to niezamierzonej aktualizacji ostatniego elementu listy, jeśli nie ma elementu o nazwie „trzy”? W takim przypadku findIndex () zwróci -1, co update () zinterpretowałoby jako indeks ostatniego elementu.
Sam Storie,
1
Nie, -1 to nie ostatni element
Vitalii Korsakov
9
@Sam Storie ma rację. Z dokumentów: „indeks może być liczbą ujemną, która jest indeksowana wstecz od końca listy. V.update (-1) aktualizuje ostatnią pozycję na liście”. facebook.github.io/immutable-js/docs/#/List/update
Gabriel Ferraz
1
Nie mogłem wywołać elementu item.set, ponieważ dla mnie element nie był niezmiennym typem, musiałem najpierw zmapować element, wywołać set, a następnie ponownie przekonwertować go z powrotem na obiekt. W tym przykładzie function (item) {return Map (item) .set ("count", 4) .toObject (); }
syclee
37

Z .setIn () możesz zrobić to samo:

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

Jeśli nie znamy indeksu wpisu, który chcemy zaktualizować. Łatwo go znaleźć za pomocą .findIndex () :

const indexOfListToUpdate = obj.get('elem').findIndex(listItem => {
  return listItem.get('name') === 'third';
});
obj = obj.setIn(['elem', indexOfListingToUpdate, 'count'], 4);

Mam nadzieję, że to pomoże!

Albert Olivé
źródło
1
brakuje ci w { }środku fromJS( ), bez nawiasów klamrowych nie jest to poprawny JS.
Andy
1
Nie nazwałbym zwróconego obiektu „arr”, ponieważ jest to obiekt. tylko arr.elem to tablica
Nir O.
21
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)

Wyjaśnienie

Aktualizowanie kolekcji Immutable.js zawsze zwraca nowe wersje tych kolekcji, pozostawiając oryginał niezmieniony. Z tego powodu nie możemy używać list[2].count = 4składni mutacji JavaScript . Zamiast tego musimy wywołać metody, podobnie jak w przypadku klas kolekcji Java.

Zacznijmy od prostszego przykładu: tylko liczby na liście.

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

Teraz, jeśli chcemy, aby zaktualizować 3rd element, zwykły tablica JS może wyglądać tak: counts[2] = 4. Ponieważ nie możemy użyć mutacji i musimy wywołać metodę, zamiast tego możemy użyć: counts.set(2, 4)- oznacza to ustawienie wartości 4w indeksie 2.

Głębokie aktualizacje

Jednak podany przykład zawiera zagnieżdżone dane. Nie możemy po prostu użyć set()na początkowej kolekcji.

Kolekcje Immutable.js mają rodzinę metod, których nazwy kończą się na „In”, które umożliwiają wprowadzanie głębszych zmian w zagnieżdżonym zestawie. Większość popularnych metod aktualizacji ma powiązaną metodę „In”. Na przykład setjest setIn. Zamiast akceptować indeks lub klucz jako pierwszy argument, te metody „In” akceptują „ścieżkę klucza”. Ścieżka klucza to tablica indeksów lub kluczy, która ilustruje, jak dotrzeć do wartości, którą chcesz zaktualizować.

W naszym przykładzie chciałeś zaktualizować element na liście pod indeksem 2, a następnie wartość w kluczu „count” w tym elemencie. Więc kluczowa ścieżka byłaby [2, "count"]. Drugi parametr setInmetody działa tak samo set, jest to nowa wartość, którą chcemy tam umieścić, więc:

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

Znalezienie właściwej ścieżki klucza

Idąc o krok dalej, powiedziałeś, że chcesz zaktualizować element, którego nazwa to „trzy”, co różni się od trzeciego elementu. Na przykład, może Twoja lista nie jest posortowana, a może element o nazwie „dwa” został wcześniej usunięty? Oznacza to, że najpierw musimy się upewnić, że faktycznie znamy prawidłową ścieżkę klucza! W tym celu możemy użyć findIndex()metody (która, nawiasem mówiąc, działa prawie dokładnie tak jak Array # findIndex ).

Po znalezieniu indeksu na liście zawierającego element, który chcemy zaktualizować, możemy podać ścieżkę klucza do wartości, którą chcemy zaktualizować:

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

NB: SetvsUpdate

Oryginalne pytanie dotyczy metod aktualizacji, a nie metod zestawu. Wyjaśnię drugi argument w tej funkcji (nazywany updater), ponieważ różni się od set(). Podczas gdy drugi argument set()to nowa wartość, której szukamy, drugi argument update()to funkcja, która przyjmuje poprzednią wartość i zwraca nową, którą chcemy. Następnie updateIn()jest wariantem „In”, update()który akceptuje ścieżkę klucza.

Załóżmy na przykład, że chcieliśmy mieć odmianę twojego przykładu, która nie tylko ustawia liczbę na 4, ale zamiast tego zwiększa istniejącą liczbę, możemy zapewnić funkcję, która dodaje ją do istniejącej wartości:

var index = list.findIndex(item => item.name === "three")
list = list.updateIn([index, "count"], value => value + 1)
Lee Byron
źródło
19

Oto, co powiedzieli oficjalni doktorzy… updateIn

Nie potrzebujesz updateIn, co dotyczy tylko struktur zagnieżdżonych. Szukasz updatemetody , która ma znacznie prostszą sygnaturę i dokumentację:

Zwraca nową Listę ze zaktualizowaną wartością w indeksie ze zwracaną wartością wywołania Updatera z istniejącą wartością lub notSetValue, jeśli indeks nie został ustawiony.

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

co, jak sugerują Map::updatedokumenty , jest „ równoważne z:list.set(index, updater(list.get(index, notSetValue))) ”.

gdzie element o nazwie „trzeci”

Nie tak działają listy. Musisz znać indeks elementu, który chcesz zaktualizować, lub musisz go wyszukać.

Jak mogę zaktualizować listę, na której element o nazwie trzeciej ma liczbę ustawioną na 4?

To powinno wystarczyć:

list = list.update(2, function(v) {
    return {id: v.id, name: v.name, count: 4};
});
Bergi
źródło
14
Nie, Twój wybór struktury danych nie jest zgodny z logiką biznesową :-) Jeśli chcesz uzyskać dostęp do elementów poprzez ich nazwy, powinieneś użyć mapy zamiast listy.
Bergi
1
@bergi, naprawdę? Więc jeśli mam, powiedzmy, listę uczniów w klasie i chcę wykonać operacje CRUD na tych danych uczniów, użycie Mapover a Listjest podejściem, które byś zasugerował? A może mówisz tak tylko dlatego, że OP powiedział „wybierz element według nazwy”, a nie „wybierz element według identyfikatora”?
David Gilbertson
2
@DavidGilbertson: CRUD? Proponuję bazę danych :-) Ale tak, mapa studencka id-> brzmi bardziej adekwatnie niż lista, chyba że masz na nich zamówienie i chcesz je wybrać według indeksu.
Bergi
1
@bergi Myślę, że zgodzimy się nie zgodzić, otrzymuję mnóstwo danych z powrotem z API w postaci tablic rzeczy z identyfikatorami, gdzie muszę zaktualizować te rzeczy według identyfikatora. To jest tablica JS i moim zdaniem powinna to być niezmienna listaJS.
David Gilbertson
1
@DavidGilbertson: Myślę, że te interfejsy API przyjmują tablice JSON dla zestawów - które są jawnie nieuporządkowane.
Bergi
12

Użyj .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
źródło
1
Dzięki za to - tak wiele okropnych, zawiłych odpowiedzi, prawdziwa odpowiedź brzmi: „jeśli chcesz zmienić listę, użyj mapy”. O to chodzi w trwałych, niezmiennych strukturach danych, nie "aktualizujesz" ich, tworzysz nową strukturę ze zaktualizowanymi wartościami; i jest to tanie, ponieważ niezmodyfikowane elementy listy są udostępniane.
Korny
2

Bardzo podoba mi się to podejście ze strony 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);
Dryden Williams
źródło
-2

Możesz użyć map:

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

Ale to będzie powtarzać się w całej kolekcji.

Aldo Porcallo
źródło