Comment mettre à jour un élément dans List avec ImmutableJS?

129

Voici ce que disent les documents officiels

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>

Il n'y a aucun moyen pour un développeur Web normal (pas un programmeur fonctionnel) de comprendre cela!

J'ai un cas assez simple (pour une approche non fonctionnelle).

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

Comment puis-je mettre à jour listoù l'élément avec le nom tiers a son nombre défini sur 4 ?

Vitalii Korsakov
la source
43
Je ne sais pas à quoi ça ressemble, mais la documentation est terrible facebook.github.io/immutable-js/docs/#/List/update
Vitalii Korsakov
71
Un vote positif sérieux pour la fouille à la documentation. Si le but de cette syntaxe est de me faire sentir stupide / inadéquat, un succès certain. Si le but, cependant, est de transmettre comment Immutable fonctionne, eh bien ...
Owen
9
Je dois être d'accord, je trouve que la documentation pour immutable.js est très frustrante. La solution acceptée pour cela utilise une méthode findIndex () que je ne vois même pas du tout dans la documentation.
RichardForrester
3
Je préfère qu'ils donnent un exemple pour chaque fonction au lieu de ces choses.
RedGiant
4
D'accord. La documentation doit avoir été écrite par un scientifique dans une bulle, pas par quelqu'un qui veut montrer quelques exemples simples. La documentation a besoin de quelques documents. Par exemple, j'aime la documentation de soulignement, beaucoup plus facile à voir des exemples pratiques.
ReduxDJ

Réponses:

119

Le cas le plus approprié est d'utiliser les deux méthodes findIndexet update.

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

PS Il n'est pas toujours possible d'utiliser Maps. Par exemple, si les noms ne sont pas uniques et que je souhaite mettre à jour tous les éléments avec les mêmes noms.

Vitalii Korsakov
la source
1
Si vous avez besoin de noms en double, utilisez des multi-cartes ou des cartes avec des tuples comme valeurs.
Bergi
5
Cela ne mettra-t-il pas à jour par inadvertance le dernier élément de la liste s'il n'y a pas d'élément avec "trois" comme nom? Dans ce cas, findIndex () renverrait -1, que update () interpréterait comme l'index du dernier élément.
Sam Storie
1
Non, -1 n'est pas le dernier élément
Vitalii Korsakov
9
@Sam Storie a raison. D'après la documentation: "l'index peut être un nombre négatif, qui est indexé depuis la fin de la liste. V.update (-1) met à jour le dernier élément de la liste." facebook.github.io/immutable-js/docs/#/List/update
Gabriel Ferraz
1
Je ne pouvais pas appeler item.set car pour moi, l'élément n'était pas de type immuable, je devais d'abord mapper l'élément, appeler set puis le reconvertir en objet à nouveau. Donc, pour cet exemple, function (item) {return Map (item) .set ("count", 4) .toObject (); }
syclee
36

Avec .setIn (), vous pouvez faire la même chose:

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

Si nous ne connaissons pas l'index de l'entrée, nous voulons mettre à jour. Il est assez facile de le trouver en utilisant .findIndex () :

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

J'espère que ça aide!

Albert Olivé
la source
1
vous manquez à l' { }intérieur fromJS( ), sans les accolades, ce n'est pas un JS valide.
Andy
1
Je n'appellerais pas l'objet retourné «arr» car il s'agit d'un objet. seul arr.elem est un tableau
Nir O.
21
var index = list.findIndex(item => item.name === "three")
list = list.setIn([index, "count"], 4)

Explication

La mise à jour des collections Immutable.js renvoie toujours les nouvelles versions de ces collections en laissant l'original inchangé. Pour cette raison, nous ne pouvons pas utiliser la list[2].count = 4syntaxe de mutation de JavaScript . Au lieu de cela, nous devons appeler des méthodes, comme nous pourrions le faire avec les classes de collection Java.

Commençons par un exemple plus simple: juste les décomptes dans une liste.

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

Maintenant , si nous voulions mettre à jour le 3ème article, un tableau JS plaine pourrait ressembler à : counts[2] = 4. Puisque nous ne pouvons pas utiliser de mutation et que nous devons appeler une méthode, nous pouvons utiliser à la place: counts.set(2, 4)- cela signifie définir la valeur 4à l'index 2.

Mises à jour profondes

L'exemple que vous avez donné a cependant des données imbriquées. Nous ne pouvons pas simplement utiliser set()sur la collection initiale.

Les collections Immutable.js ont une famille de méthodes dont les noms se terminent par "In" qui vous permettent d'apporter des modifications plus profondes dans un ensemble imbriqué. Les méthodes de mise à jour les plus courantes ont une méthode "In" associée. Par exemple car setil y en a setIn. Au lieu d'accepter un index ou une clé comme premier argument, ces méthodes "In" acceptent un "chemin de clé". Le chemin de clé est un tableau d'index ou de clés qui illustre comment accéder à la valeur que vous souhaitez mettre à jour.

Dans votre exemple, vous vouliez mettre à jour l'élément dans la liste à l'index 2, puis la valeur à la clé «count» dans cet élément. Le chemin clé serait donc [2, "count"]. Le deuxième paramètre de la setInméthode fonctionne exactement comme set, c'est la nouvelle valeur que nous voulons y mettre, donc:

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

Trouver le bon chemin clé

Pour aller plus loin, vous avez en fait dit que vous vouliez mettre à jour l'élément dont le nom est "trois", ce qui est différent du troisième élément. Par exemple, peut-être que votre liste n'est pas triée, ou peut-être que l'élément nommé «deux» a été supprimé plus tôt? Cela signifie d'abord que nous devons nous assurer que nous connaissons réellement le chemin de clé correct! Pour cela, nous pouvons utiliser la findIndex()méthode (qui, d'ailleurs, fonctionne presque exactement comme Array # findIndex ).

Une fois que nous avons trouvé l'index dans la liste qui contient l'élément que nous voulons mettre à jour, nous pouvons fournir le chemin d'accès de la clé à la valeur que nous souhaitons mettre à jour:

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

NB: SetvsUpdate

La question d'origine mentionne les méthodes de mise à jour plutôt que les méthodes définies. Je vais expliquer le deuxième argument de cette fonction (appelée updater), car il est différent de set(). Alors que le deuxième argument de set()est la nouvelle valeur que nous voulons, le deuxième argument de update()est une fonction qui accepte la valeur précédente et renvoie la nouvelle valeur que nous voulons. Ensuite, updateIn()est la variante "In" de update()qui accepte un chemin clé.

Supposons, par exemple, que nous voulions une variante de votre exemple qui ne se contente pas de définir le nombre 4, mais qui incrémente à la place le nombre existant, nous pourrions fournir une fonction qui en ajoute une à la valeur existante:

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

Voici ce que disent les documents officiels… updateIn

Vous n'en avez pas besoin updateIn, c'est uniquement pour les structures imbriquées. Vous recherchez la updateméthode , qui a une signature et une documentation beaucoup plus simples:

Renvoie une nouvelle liste avec une valeur mise à jour à l'index avec la valeur de retour de l'appel du programme de mise à jour avec la valeur existante, ou notSetValue si l'index n'a pas été défini.

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

qui, comme le suggèrent les Map::updatedocuments , est " équivalent à:list.set(index, updater(list.get(index, notSetValue))) ".

où élément avec le nom "troisième"

Ce n'est pas ainsi que fonctionnent les listes. Vous devez connaître l'index de l'élément que vous souhaitez mettre à jour, ou vous devez le rechercher.

Comment puis-je mettre à jour la liste où l'élément avec le nom tiers a son nombre défini sur 4?

Cela devrait le faire:

list = list.update(2, function(v) {
    return {id: v.id, name: v.name, count: 4};
});
Bergi
la source
14
Non, votre choix de structure de données ne répond pas à la logique métier :-) Si vous souhaitez accéder aux éléments par leur nom, vous devez utiliser une carte au lieu d'une liste.
Bergi
1
@bergi, vraiment? Donc, si j'ai, par exemple, une liste d'élèves dans une classe et que je souhaite effectuer des opérations CRUD sur ces données d'élève, en utilisant un Mapsur a Listest l'approche que vous suggéreriez? Ou dites-vous cela uniquement parce que OP a dit "sélectionner l'élément par nom" et non "sélectionner l'élément par identifiant"?
David Gilbertson
2
@DavidGilbertson: CRUD? Je suggérerais une base de données :-) Mais oui, une carte id-> Student semble plus appropriée qu'une liste, à moins que vous n'ayez une commande et que vous vouliez les sélectionner par index.
Bergi
1
@bergi Je suppose que nous serons d'accord pour ne pas être d'accord, je récupère beaucoup de données des API sous la forme de tableaux de choses avec des ID, où je dois mettre à jour ces choses par ID. Ceci est un JS Array, et dans mon esprit, devrait donc être une liste immutableJS.
David Gilbertson
1
@DavidGilbertson: Je pense que ces API prennent des tableaux JSON pour des ensembles - qui ne sont explicitement pas ordonnés.
Bergi
12

Utilisez .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
la source
1
Merci pour cela - tant de terribles réponses alambiquées ici, la vraie réponse est "si vous voulez changer une liste, utilisez la carte". C'est tout l'intérêt des structures de données immuables persistantes, vous ne les «mettez pas à jour», vous créez une nouvelle structure avec les valeurs mises à jour; et cela ne coûte pas cher car les éléments de liste non modifiés sont partagés.
Korny
2

J'aime vraiment cette approche du site Web de 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
la source
-2

Vous pouvez utiliser map:

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

Mais cela va itérer sur toute la collection.

Aldo Porcallo
la source