Wie aktualisiere ich ein Element in der Liste mit ImmutableJS?


129

Hier ist, was offizielle Dokumente sagten

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>

Normaler Webentwickler (kein funktionierender Programmierer) würde das auf keinen Fall verstehen!

Ich habe einen ziemlich einfachen Fall (für einen nicht funktionalen Ansatz).

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

Wie kann ich aktualisieren, listwo für Elemente mit dem dritten Namen die Anzahl auf 4 gesetzt ist ?


43
Ich weiß nicht, wie es aussieht, aber die Dokumentation ist schrecklich facebook.github.io/immutable-js/docs/#/List/update
Vitalii Korsakov

71
Ernsthafte Gegenstimme für die Ausgrabung in der Dokumentation. Wenn das Ziel dieser Syntax darin besteht, dass ich mich dumm / unangemessen fühle, ist dies definitiv ein Erfolg. Wenn das Ziel jedoch darin besteht, zu vermitteln, wie unveränderlich funktioniert, dann ...
Owen

9
Ich muss zustimmen, ich finde die Dokumentation für immutable.js ernsthaft frustrierend. Die akzeptierte Lösung hierfür verwendet eine Methode findIndex (), die ich in den Dokumenten überhaupt nicht sehe.
RichardForrester

3
Ich möchte lieber, dass sie für jede Funktion ein Beispiel geben, anstatt für diese Dinge.
RedGiant

4
Einverstanden. Die Dokumentation muss von einem Wissenschaftler in einer Blase geschrieben worden sein, nicht von jemandem, der einige einfache Beispiele zeigen möchte. Die Dokumentation benötigt einige Dokumentation. Zum Beispiel gefällt mir die Unterstreichungsdokumentation, die praktische Beispiele viel einfacher zu sehen ist.
ReduxDJ

Antworten:


119

Am besten ist es, sowohl findIndexals auch updateMethoden zu verwenden.

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

PS Es ist nicht immer möglich, Karten zu verwenden. ZB wenn Namen nicht eindeutig sind und ich alle Elemente mit den gleichen Namen aktualisieren möchte.


1
Wenn Sie doppelte Namen benötigen, verwenden Sie Multimaps - oder Maps mit Tupeln als Werte.
Bergi

5
Wird das letzte Element der Liste nicht versehentlich aktualisiert, wenn es kein Element mit dem Namen "drei" gibt? In diesem Fall würde findIndex () -1 zurückgeben, wobei update () als Index des letzten Elements interpretiert wird.
Sam Storie

1
Nein, -1 ist nicht das letzte Element
Vitalii Korsakov

9
@ Sam Storie ist richtig. Aus den Dokumenten: "Index kann eine negative Zahl sein, die vom Ende der Liste zurückindiziert. V.update (-1) aktualisiert das letzte Element in der Liste." facebook.github.io/immutable-js/docs/#/List/update
Gabriel Ferraz

1
Ich konnte item.set nicht aufrufen, da item für mich kein unveränderlicher Typ war. Ich musste zuerst das Element zuordnen, set aufrufen und es dann wieder in ein Objekt konvertieren. In diesem Beispiel ist function (item) {return Map (item) .set ("count", 4) .toObject (); }
Syclee

36

Mit .setIn () können Sie dasselbe tun:

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

Wenn wir den Index des Eintrags nicht kennen, möchten wir ihn aktualisieren. Es ist ziemlich einfach, es mit .findIndex () zu finden :

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

Ich hoffe es hilft!


1
Sie fehlen im { }Inneren fromJS( ), ohne die geschweiften Klammern ist es nicht gültig JS.
Andy

1
Ich würde das zurückgegebene Objekt nicht 'arr' nennen, da es ein Objekt ist. nur arr.elem ist ein Array
Nir O.

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

Erläuterung

Durch das Aktualisieren der Immutable.js-Sammlungen werden immer neue Versionen dieser Sammlungen zurückgegeben, wobei das Original unverändert bleibt. Aus diesem Grund können wir die list[2].count = 4Mutationssyntax von JavaScript nicht verwenden . Stattdessen müssen wir Methoden aufrufen, ähnlich wie bei Java-Auflistungsklassen.

Beginnen wir mit einem einfacheren Beispiel: Nur die Anzahl in einer Liste.

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

Wenn wir nun das dritte Element aktualisieren möchten, könnte ein einfaches JS-Array folgendermaßen aussehen : counts[2] = 4. Da wir keine Mutation verwenden können und eine Methode aufrufen müssen, können wir stattdessen Folgendes verwenden: counts.set(2, 4)- Das bedeutet, dass der Wert 4am Index festgelegt wird 2.

Tiefe Updates

Das von Ihnen angegebene Beispiel enthält jedoch verschachtelte Daten. Wir können nicht nur set()für die erste Sammlung verwenden.

Immutable.js-Sammlungen verfügen über eine Methodenfamilie mit Namen, die mit "In" enden, sodass Sie tiefere Änderungen in einer verschachtelten Gruppe vornehmen können. Die meisten gängigen Aktualisierungsmethoden haben eine verwandte "In" -Methode. Zum Beispiel setgibt es setIn. Anstatt einen Index oder einen Schlüssel als erstes Argument zu akzeptieren, akzeptieren diese "In" -Methoden einen "Schlüsselpfad". Der Schlüsselpfad ist ein Array von Indizes oder Schlüsseln, die veranschaulichen, wie Sie zu dem Wert gelangen, den Sie aktualisieren möchten.

In Ihrem Beispiel wollten Sie das Element in der Liste bei Index 2 und dann den Wert beim Schlüssel "count" innerhalb dieses Elements aktualisieren. Der Schlüsselpfad wäre also [2, "count"]. Der zweite Parameter der setInMethode funktioniert genauso wie setder neue Wert, den wir dort einfügen möchten. Also:

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

Den richtigen Schlüsselpfad finden

Sie gingen noch einen Schritt weiter und sagten tatsächlich, Sie wollten das Element aktualisieren, wobei der Name "drei" ist, was sich von nur dem dritten Element unterscheidet. Zum Beispiel, vielleicht ist Ihre Liste nicht sortiert, oder vielleicht wurde dort das Element "zwei" früher entfernt? Das heißt, wir müssen zuerst sicherstellen, dass wir tatsächlich den richtigen Schlüsselpfad kennen! Hierfür können wir die findIndex()Methode verwenden (die übrigens fast genau wie Array # findIndex funktioniert ).

Sobald wir den Index in der Liste gefunden haben, der das Element enthält, das wir aktualisieren möchten, können wir den Schlüsselpfad zu dem Wert angeben, den wir aktualisieren möchten:

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

NB: Setvs.Update

In der ursprünglichen Frage werden die Aktualisierungsmethoden und nicht die festgelegten Methoden erwähnt. Ich werde das zweite Argument in dieser Funktion (aufgerufen updater) erklären , da es sich von unterscheidet set(). Während das zweite Argument to set()der gewünschte neue Wert ist, ist das zweite Argument to update()eine Funktion, die den vorherigen Wert akzeptiert und den gewünschten neuen Wert zurückgibt. Dann updateIn()ist die "In" -Variante, update()die einen Schlüsselpfad akzeptiert.

Angenommen, wir wollten eine Variation Ihres Beispiels, bei der nicht nur die Anzahl festgelegt 4, sondern stattdessen die vorhandene Anzahl erhöht wurde. Wir könnten eine Funktion bereitstellen, die dem vorhandenen Wert eine hinzufügt:

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

19

Hier ist, was offizielle Dokumente sagten ... updateIn

Sie brauchen nicht updateIn, was nur für verschachtelte Strukturen ist. Sie suchen nach der updateMethode , die eine viel einfachere Signatur und Dokumentation hat:

Gibt eine neue Liste mit einem aktualisierten Wert am Index mit dem Rückgabewert des aufrufenden Updaters mit dem vorhandenen Wert oder notSetValue zurück, wenn der Index nicht festgelegt wurde.

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

was, wie die Map::updateDokumente vorschlagen, " äquivalent zu:list.set(index, updater(list.get(index, notSetValue))) " ist.

wobei Element mit dem Namen "dritter"

So funktionieren Listen nicht. Sie müssen den Index des Elements kennen, das Sie aktualisieren möchten, oder Sie müssen danach suchen.

Wie kann ich eine Liste aktualisieren, bei der die Anzahl der Elemente mit dem dritten Namen auf 4 gesetzt ist?

Dies sollte es tun:

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

14
Nein, Ihre Wahl der Datenstruktur entspricht nicht der Geschäftslogik :-) Wenn Sie auf Elemente mit ihrem Namen zugreifen möchten, sollten Sie eine Karte anstelle einer Liste verwenden.
Bergi

1
@bergi, wirklich? Wenn ich also beispielsweise eine Liste von Schülern in einer Klasse habe und CRUD-Operationen mit diesen Schülerdaten durchführen möchte, ist der von Ihnen vorgeschlagene Ansatz ein Mapüber a List? Oder sagen Sie das nur, weil OP "Artikel nach Namen auswählen" und nicht "Artikel nach ID auswählen" sagte?
David Gilbertson

2
@ DavidGilbertson: CRUD? Ich würde eine Datenbank vorschlagen :-) Aber ja, eine ID-> Studentenkarte klingt geeigneter als eine Liste, es sei denn, Sie haben eine Reihenfolge und möchten sie nach Index auswählen.
Bergi

1
@bergi Ich denke, wir werden zustimmen, nicht zuzustimmen. Ich erhalte viele Daten von APIs in Form von Arrays von Dingen mit IDs zurück, wobei ich diese Dinge nach ID aktualisieren muss. Dies ist ein JS-Array und sollte daher meiner Meinung nach eine unveränderliche JS-Liste sein.
David Gilbertson

1
@DavidGilbertson: Ich denke, diese APIs verwenden JSON-Arrays für Sets - die explizit ungeordnet sind.
Bergi

12

Verwenden Sie .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
Vielen Dank dafür - so viele schrecklich verschlungene Antworten hier, die eigentliche Antwort lautet "Wenn Sie eine Liste ändern möchten, verwenden Sie die Karte". Das ist der springende Punkt bei dauerhaften unveränderlichen Datenstrukturen. Sie "aktualisieren" sie nicht, sondern erstellen eine neue Struktur mit den darin enthaltenen aktualisierten Werten. und das ist billig, weil die unveränderten Listenelemente gemeinsam genutzt werden.
Korny

2

Ich mag diesen Ansatz von der thomastuts- Website sehr:

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

Sie können verwenden map:

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

Dies wird jedoch die gesamte Sammlung durchlaufen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.