Come aggiornare l'elemento all'interno di List con ImmutableJS?


129

Ecco cosa hanno detto i documenti ufficiali

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>

Non è possibile che un normale sviluppatore web (non programmatore funzionale) lo capisca!

Ho un caso piuttosto semplice (per un approccio non funzionale).

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

Come posso aggiornare listdove elemento con nome terzo ha il suo conteggio impostato su 4 ?


43
Non so come sia, ma la documentazione è terribile facebook.github.io/immutable-js/docs/#/List/update
Vitalii Korsakov

71
Grave upgrade per lo scavo alla documentazione. Se l'obiettivo di quella sintassi è farmi sentire stupido / inadeguato, un successo definito. Se l'obiettivo, tuttavia, è quello di comunicare come funziona l'Immutabile, beh ...
Owen,

9
Devo essere d'accordo, trovo che la documentazione di immutable.js sia seriamente frustrante. La soluzione accettata per questo utilizza un metodo findIndex () che non vedo nemmeno nei documenti.
RichardForrester,

3
Preferirei dare un esempio per ogni funzione invece di quelle cose.
RedGiant

4
Concordato. La documentazione deve essere stata scritta da uno scienziato in una bolla, non da qualcuno che vuole mostrare alcuni semplici esempi. La documentazione necessita di documentazione. Ad esempio, mi piace la documentazione di sottolineatura, molto più facile vedere esempi pratici.
ReduxDJ il

Risposte:


119

Il caso più appropriato è usare entrambi findIndexe updatemetodi.

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

PS Non è sempre possibile utilizzare Maps. Ad esempio, se i nomi non sono univoci e voglio aggiornare tutti gli articoli con gli stessi nomi.


1
Se hai bisogno di nomi duplicati, usa multimappa o mappe con tuple come valori.
Bergi,

5
Non aggiornerà inavvertitamente l'ultimo elemento dell'elenco se non vi è alcun elemento con "tre" come nome? In tal caso findIndex () restituisce -1, che update () interpreterebbe come l'indice dell'ultimo elemento.
Sam Storie,

1
No, -1 non è l'ultimo elemento
Vitalii Korsakov,

9
@Sam Storie ha ragione. Dai documenti: "L'indice può essere un numero negativo, che torna indietro dalla fine dell'elenco. V.update (-1) aggiorna l'ultimo elemento dell'elenco." facebook.github.io/immutable-js/docs/#/List/update
Gabriel Ferraz

1
Non potevo chiamare item.set perché per me l'articolo non era di tipo Immutabile, dovevo prima mappare l'oggetto, chiamare set e poi riconvertirlo in oggetto. Quindi, per questo esempio, function (item) {return Map (item) .set ("count", 4) .toObject (); }
syclee

36

Con .setIn () puoi fare lo stesso:

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

Se non conosciamo l'indice della voce che vogliamo aggiornare. È abbastanza facile trovarlo usando .findIndex () :

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

Spero che sia d'aiuto!


1
ti stai perdendo { }dentro fromJS( ), senza le parentesi graffe non è valido JS.
Andy,

1
Non chiamerei l'oggetto restituito 'arr' in quanto è un oggetto. solo arr.elem è un array
Nir O.

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

Spiegazione

L'aggiornamento delle raccolte Immutable.js restituisce sempre nuove versioni di tali raccolte lasciando invariato l'originale. Per questo motivo, non possiamo usare la list[2].count = 4sintassi di mutazione di JavaScript . Invece dobbiamo chiamare metodi, proprio come potremmo fare con le classi di raccolta Java.

Cominciamo con un esempio più semplice: solo i conteggi in un elenco.

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

Ora, se volessimo aggiornare il terzo elemento, un array di JS pianura potrebbe essere simile: counts[2] = 4. Dato che non possiamo usare la mutazione e dobbiamo chiamare un metodo, invece possiamo usare: counts.set(2, 4)- ciò significa impostare il valore 4sull'indice 2.

Aggiornamenti approfonditi

L'esempio che hai fornito ha comunque dei dati nidificati. Non possiamo limitarci a utilizzare set()la raccolta iniziale.

Le raccolte Immutable.js hanno una famiglia di metodi con nomi che terminano con "In" che consentono di apportare modifiche più profonde in un set nidificato. I metodi di aggiornamento più comuni hanno un metodo "In" correlato. Ad esempio perché setc'èsetIn . Invece di accettare un indice o una chiave come primo argomento, questi metodi "In" accettano un "percorso chiave". Il percorso della chiave è una matrice di indici o chiavi che illustra come arrivare al valore che si desidera aggiornare.

Nel tuo esempio, volevi aggiornare l'elemento nell'elenco all'indice 2, quindi il valore nella chiave "conta" all'interno di quell'elemento. Quindi il percorso chiave sarebbe [2, "count"]. Il secondo parametro del setInmetodo funziona esattamente come set, è il nuovo valore che vogliamo mettere lì, quindi:

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

Trovare il giusto percorso chiave

Andando oltre, hai effettivamente detto di voler aggiornare l'elemento in cui il nome è "tre", che è diverso dal solo 3 ° elemento. Ad esempio, forse il tuo elenco non è ordinato o forse lì l'elemento chiamato "due" è stato rimosso in precedenza? Ciò significa che prima dobbiamo assicurarci di conoscere effettivamente il percorso chiave corretto! Per questo possiamo usare il findIndex()metodo (che, a proposito, funziona quasi esattamente come Array # findIndex ).

Una volta trovato l'indice nell'elenco che contiene l'elemento che vogliamo aggiornare, possiamo fornire il percorso chiave al valore che vogliamo aggiornare:

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

NB: SetvsUpdate

La domanda originale menziona i metodi di aggiornamento piuttosto che i metodi impostati. Spiegherò il secondo argomento in quella funzione (chiamata updater), poiché è diverso da set(). Mentre il secondo argomento set()è il nuovo valore che vogliamo, il secondo argomento update()è una funzione che accetta il valore precedente e restituisce il nuovo valore desiderato. Quindi, updateIn()è la variazione "In" diupdate() cui accetta un percorso chiave.

Supponiamo, ad esempio, che volessimo una variante del tuo esempio che non solo imposti il ​​conteggio su 4, ma aumentasse invece il conteggio esistente, potremmo fornire una funzione che ne aggiunga uno al valore esistente:

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

19

Ecco cosa hanno detto i documenti ufficiali ... updateIn

Non è necessario updateIn, il che è solo per strutture nidificate. Stai cercando il updatemetodo , che ha una firma e una documentazione molto più semplici:

Restituisce un nuovo Elenco con un valore aggiornato all'indice con il valore di ritorno del programma di aggiornamento chiamante con il valore esistente oppure notSetValue se l'indice non è stato impostato.

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

che, come suggeriscono i Map::updatedocumenti , è " equivalente a:list.set(index, updater(list.get(index, notSetValue))) ".

dove elemento con nome "terzo"

Non è così che funzionano le liste. Devi conoscere l'indice dell'elemento che vuoi aggiornare o devi cercarlo.

Come posso aggiornare l'elenco in cui l'elemento con nome terzo ha il suo conteggio impostato su 4?

Questo dovrebbe farlo:

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

14
No, la scelta della struttura dei dati non soddisfa la logica aziendale :-) Se si desidera accedere agli elementi in base al loro nome, è necessario utilizzare una mappa anziché un elenco.
Bergi,

1
@bergi, davvero? Quindi, se ho, per esempio, un elenco di studenti in una classe e voglio fare operazioni CRUD su questi dati degli studenti, usare un Mapover Listè l'approccio che suggeriresti? O lo dici solo perché OP ha detto "seleziona articolo per nome" e non "seleziona articolo per ID"?
David Gilbertson,

2
@DavidGilbertson: CRUD? Suggerirei un database :-) Ma sì, una mappa degli studenti id-> sembra più appropriata di un elenco, a meno che tu non abbia un ordine su di essi e desideri selezionarli per indice.
Bergi,

1
@bergi Immagino che accetteremo di non essere d'accordo, ricevo molti dati dalle API sotto forma di array di cose con ID, dove devo aggiornare quelle cose tramite ID. Questo è un array JS, e secondo me dovrebbe quindi essere un elenco immutabile di JS.
David Gilbertson,

1
@DavidGilbertson: Penso che queste API prendano array JSON per set - che sono esplicitamente non ordinati.
Bergi,

12

Usa .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
Grazie per questo - così tante terribili risposte contorte qui, la vera risposta è "se vuoi cambiare un elenco, usa la mappa". Questo è il punto centrale delle strutture di dati immutabili persistenti, non le si "aggiorna", si crea una nuova struttura con i valori aggiornati al suo interno; ed è economico farlo perché gli elementi dell'elenco non modificati sono condivisi.
Korny,

2

Mi piace molto questo approccio dal sito Web 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

Puoi usare map:

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

Ma questo ripeterà l'intera collezione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.