Status als Array von Objekten gegen Objekt, das durch id verschlüsselt ist

94

In dem Kapitel zum Entwerfen der Statusform wird in den Dokumenten vorgeschlagen, Ihren Status in einem Objekt mit ID zu speichern:

Behalten Sie jede Entität in einem Objekt bei, das mit einer ID als Schlüssel gespeichert ist, und verwenden Sie IDs, um auf sie von anderen Entitäten oder Listen zu verweisen.

Sie sagen weiter

Stellen Sie sich den Status der App als Datenbank vor.

Ich arbeite an der Statusform für eine Liste von Filtern, von denen einige geöffnet sind (sie werden in einem Popup angezeigt) oder ausgewählte Optionen haben. Als ich "Stellen Sie sich den Status der App als Datenbank vor" las, dachte ich darüber nach, sie als JSON-Antwort zu betrachten, da sie von einer API zurückgegeben wird (die selbst von einer Datenbank unterstützt wird).

Also dachte ich daran

[{
    id: '1',
    name: 'View',
    open: false,
    options: ['10', '11', '12', '13'],
    selectedOption: ['10'],
    parent: null,
  },
  {
    id: '10',
    name: 'Time & Fees',
    open: false,
    options: ['20', '21', '22', '23', '24'],
    selectedOption: null,
    parent: '1',
  }]

In den Dokumenten wird jedoch ein ähnliches Format vorgeschlagen

{
   1: { 
    name: 'View',
    open: false,
    options: ['10', '11', '12', '13'],
    selectedOption: ['10'],
    parent: null,
  },
  10: {
    name: 'Time & Fees',
    open: false,
    options: ['20', '21', '22', '23', '24'],
    selectedOption: null,
    parent: '1',
  }
}

Theoretisch sollte es keine Rolle spielen, solange die Daten serialisierbar sind (unter der Überschrift "State") .

Also ging ich glücklich mit dem Ansatz der Anordnung von Objekten vor, bis ich meinen Reduzierer schrieb.

Mit dem objektschlüsselgesteuerten ID-Ansatz (und der liberalen Verwendung der Spread-Syntax) wird der OPEN_FILTERTeil des Reduzierers

switch (action.type) {
  case OPEN_FILTER: {
    return { ...state, { ...state[action.id], open: true } }
  }

Während der Array-of-Objects-Ansatz ausführlicher ist (und von der Hilfsfunktion abhängt)

switch (action.type) {
   case OPEN_FILTER: {
      // relies on getFilterById helper function
      const filter = getFilterById(state, action.id);
      const index = state.indexOf(filter);
      return state
        .slice(0, index)
        .concat([{ ...filter, open: true }])
        .concat(state.slice(index + 1));
    }
    ...

Meine Fragen sind also dreifach:

1) Ist die Einfachheit des Reduzierers die Motivation für den objektschlüsselgesteuerten Ansatz? Gibt es andere Vorteile für diese Zustandsform?

und

2) Es scheint, dass der objektschlüsselgesteuerte Ansatz es schwieriger macht, mit Standard-JSON-Ein- / Ausgängen für eine API umzugehen. (Deshalb habe ich mich in erster Linie für das Array von Objekten entschieden.) Wenn Sie sich also für diesen Ansatz entscheiden, verwenden Sie einfach eine Funktion, um sie zwischen dem JSON-Format und dem Statusformformat hin und her zu transformieren? Das scheint klobig. (Wenn Sie diesen Ansatz befürworten, ist dies Teil Ihrer Argumentation, dass dies weniger klobig ist als der oben beschriebene Reduzierer für Objektarrays?)

und

3) Ich weiß, dass Dan Abramov Redux so entworfen hat, dass es theoretisch unabhängig von Zustandsdatenstrukturen ist (wie in "Konventionell ist der Zustand der obersten Ebene ein Objekt oder eine andere Schlüsselwertsammlung wie eine Karte, aber technisch kann es jede sein." Typ , " Betonung meiner). Aber angesichts der obigen Ausführungen ist es nur "empfohlen", ein Objekt mit ID zu versehen, oder gibt es andere unvorhergesehene Schwachstellen, auf die ich stoßen werde, wenn ich eine Reihe von Objekten verwende, die es so machen, dass ich das einfach abbrechen sollte planen und versuchen, bei einem Objekt zu bleiben, das mit einem ID versehen ist?

Nickcoxdotme
quelle
2
Dies ist eine interessante Frage, die ich auch hatte, um einen Einblick zu geben, obwohl ich dazu neige, mich in Redux anstelle von Arrays zu normalisieren (nur weil das Nachschlagen einfacher ist), finde ich, dass die Sortierung normalisiert wird, wenn Sie den normalisierten Ansatz wählen Ein Problem, weil Sie nicht die gleiche Struktur wie das Array erhalten, sodass Sie gezwungen sind, sich selbst zu sortieren.
Robert Saunders
Ich sehe ein Problem im Ansatz "Objektschlüssel durch ID", dies ist jedoch nicht häufig, aber wir müssen diesen Fall beim Schreiben einer UI-Anwendung berücksichtigen. Was ist, wenn ich die Reihenfolge der Entität mithilfe eines Drag-Drop-Elements ändern möchte, das als geordnete Liste aufgeführt ist? Normalerweise schlägt der Ansatz "Objektschlüssel durch ID" hier fehl, und ich würde sicherlich einen Ansatz mit einer Reihe von Objekten wählen, um solch großzügige Probleme zu vermeiden. Es könnte mehr geben, als daran zu denken, dies hier zu teilen
Kunal Navhate
Wie können Sie ein Objekt aus Objekten sortieren? Das scheint unmöglich.
David Vielhuber
@ DavidVielhuber Du meinst neben etwas wie lodash's sort_by? const sorted = _.sortBy(collection, 'attribute');
Nickcoxdotme
Ja. Derzeit konvertieren wir diese Objekte in Arrays innerhalb einer vue berechneten Eigenschaft
David Vielhuber

Antworten:

46

Q1: Die Einfachheit des Reduzierers resultiert daraus, dass das Array nicht durchsucht werden muss, um den richtigen Eintrag zu finden. Das Array nicht durchsuchen zu müssen, ist der Vorteil. Selektoren und andere Datenzugriffsberechtigte können und werden häufig über diese Elemente zugreifen id. Das Durchsuchen des Arrays nach jedem Zugriff wird zu einem Leistungsproblem. Wenn Ihre Arrays größer werden, verschlechtert sich das Leistungsproblem erheblich. Je komplexer Ihre App wird und Daten an mehreren Stellen angezeigt und gefiltert werden, desto schlimmer wird das Problem. Die Kombination kann schädlich sein. Durch den Zugriff auf die Elemente über idändert sich die Zugriffszeit von O(n)auf O(1), was für große n(hier Array-Elemente) einen großen Unterschied macht.

F2: Sie können verwenden normalizr, um bei der Konvertierung von der API zum Speicher zu helfen. Ab normalizr V3.1.0 können Sie denormalize verwenden, um in die andere Richtung zu gehen. Allerdings sind Apps häufig mehr Verbraucher als Hersteller von Daten, weshalb die Konvertierung in ein Geschäft in der Regel häufiger erfolgt.

F3: Die Probleme, auf die Sie bei der Verwendung eines Arrays stoßen, sind weniger Probleme mit der Speicherkonvention und / oder Inkompatibilitäten als vielmehr Leistungsprobleme.

DDS
quelle
Normalisierer ist wieder das, was sicherlich Schmerzen verursachen würde, wenn wir die Defs im Backend ändern. Das muss also jedes Mal auf dem neuesten Stand sein
Kunal Navhate
12

Stellen Sie sich den Status der App als Datenbank vor.

Das ist die Schlüsselidee.

1) Wenn Sie Objekte mit eindeutigen IDs haben, können Sie diese ID immer verwenden, wenn Sie auf das Objekt verweisen. Daher müssen Sie die Mindestdatenmenge zwischen Aktionen und Reduzierungen übergeben. Es ist effizienter als die Verwendung von array.find (...). Wenn Sie den Array-Ansatz verwenden, müssen Sie das gesamte Objekt übergeben, und das kann sehr bald chaotisch werden. Möglicherweise erstellen Sie das Objekt auf verschiedenen Reduzierungen, Aktionen oder sogar im Container neu (das möchten Sie nicht). Ansichten können immer das vollständige Objekt abrufen, auch wenn der zugehörige Reduzierer nur die ID enthält, da beim Zuordnen des Status die Sammlung irgendwo abgerufen wird (die Ansicht erhält den gesamten Status, um sie den Eigenschaften zuzuordnen). Aufgrund all dessen, was ich gesagt habe, haben Aktionen am Ende die minimale Menge an Parametern und reduzieren die minimale Menge an Informationen. Probieren Sie es aus.

2) Die Verbindung zur API sollte sich nicht auf die Architektur Ihres Speichers und Ihrer Reduzierungen auswirken. Aus diesem Grund haben Sie Maßnahmen, um die Trennung von Bedenken aufrechtzuerhalten. Fügen Sie einfach Ihre Konvertierungslogik in ein wiederverwendbares Modul ein und aus der API heraus, importieren Sie dieses Modul in die Aktionen, die die API verwenden, und das sollte es sein.

3) Ich habe Arrays für Strukturen mit IDs verwendet, und dies sind die unvorhergesehenen Konsequenzen, die ich erlitten habe:

  • Objekte im gesamten Code ständig neu erstellen
  • Weitergabe unnötiger Informationen an Reduzierer und Maßnahmen
  • Infolgedessen schlechter, nicht sauberer und nicht skalierbarer Code.

Am Ende habe ich meine Datenstruktur geändert und viel Code neu geschrieben. Sie wurden gewarnt, bitte bringen Sie sich nicht in Schwierigkeiten.

Ebenfalls:

4) Die meisten Sammlungen mit IDs sollen die ID als Referenz für das gesamte Objekt verwenden. Dies sollten Sie nutzen. Die API-Aufrufe erhalten die ID und dann den Rest der Parameter, ebenso wie Ihre Aktionen und Reduzierungen.

Marco Scabbiolo
quelle
Ich stoße auf ein Problem, bei dem wir eine App mit vielen Daten (1000 bis 10.000) haben, die nach ID in einem Objekt im Redux-Speicher gespeichert sind. In den Ansichten verwenden alle sortierte Arrays, um Zeitreihendaten anzuzeigen. Dies bedeutet, dass jedes Mal, wenn ein erneutes Rendern durchgeführt wird, das gesamte Objekt übernommen, in ein Array konvertiert und sortiert werden muss. Ich wurde beauftragt, die Leistung der App zu verbessern. Ist dies ein Anwendungsfall, bei dem es sinnvoller ist, Ihre Daten in einem sortierten Array zu speichern und anstelle eines Objekts die binäre Suche zum Löschen und Aktualisieren zu verwenden?
William Chou
Am Ende musste ich einige andere Hash-Maps erstellen, die aus diesen Daten abgeleitet wurden, um die Rechenzeit für Updates zu minimieren. Das Aktualisieren aller verschiedenen Ansichten erfordert daher eine eigene Aktualisierungslogik. Zuvor haben alle Komponenten das Objekt aus dem Speicher genommen und die Datenstrukturen neu erstellt, die für die Erstellung erforderlich sind. Die einzige Möglichkeit, die ich mir vorstellen kann, um ein minimales Ruckeln in der Benutzeroberfläche sicherzustellen, ist die Verwendung eines Web-Workers, um die Konvertierung von Objekt zu Array durchzuführen. Der Kompromiss hierfür ist das einfachere Abrufen und Aktualisieren der Logik, da alle Komponenten nur von einem Datentyp abhängen, in den gelesen und geschrieben werden soll.
William Chou
8

1) Ist die Einfachheit des Reduzierers die Motivation für den objektschlüsselgesteuerten Ansatz? Gibt es andere Vorteile für diese Zustandsform?

Der Hauptgrund, warum Sie Entitäten in Objekten behalten möchten, die mit IDs als Schlüssel gespeichert sind (auch als normalisiert bezeichnet ), ist, dass die Arbeit mit tief verschachtelten Objekten (was Sie normalerweise von REST-APIs in einer komplexeren App erhalten) sehr umständlich ist. sowohl für Ihre Komponenten als auch für Ihre Reduzierstücke.

Es ist etwas schwierig, die Vorteile eines normalisierten Zustands anhand Ihres aktuellen Beispiels zu veranschaulichen (da Sie keine tief verschachtelte Struktur haben ). Angenommen, die Optionen (in Ihrem Beispiel) hatten ebenfalls einen Titel und wurden von Benutzern in Ihrem System erstellt. Das würde die Antwort stattdessen ungefähr so ​​aussehen lassen:

[{
  id: 1,
  name: 'View',
  open: false,
  options: [
    {
      id: 10, 
      title: 'Option 10',
      created_by: { 
        id: 1, 
        username: 'thierry' 
      }
    },
    {
      id: 11, 
      title: 'Option 11',
      created_by: { 
        id: 2, 
        username: 'dennis'
      }
    },
    ...
  ],
  selectedOption: ['10'],
  parent: null,
},
...
]

Angenommen, Sie möchten eine Komponente erstellen, die eine Liste aller Benutzer anzeigt, die Optionen erstellt haben. Dazu müssten Sie zuerst alle Elemente anfordern, dann alle Optionen durchlaufen und zuletzt den erstellten_Benutzernamen abrufen.

Eine bessere Lösung wäre, die Reaktion zu normalisieren in:

results: [1],
entities: {
  filterItems: {
    1: {
      id: 1,
      name: 'View',
      open: false,
      options: [10, 11],
      selectedOption: [10],
      parent: null
    }
  },
  options: {
    10: {
      id: 10,
      title: 'Option 10',
      created_by: 1
    },
    11: {
      id: 11,
      title: 'Option 11',
      created_by: 2
    }
  },
  optionCreators: {
    1: {
      id: 1,
      username: 'thierry',
    },
    2: {
      id: 2,
      username: 'dennis'
    }
  }
}

Mit dieser Struktur ist es viel einfacher und effizienter, alle Benutzer aufzulisten, die Optionen erstellt haben (wir haben sie in entity.optionCreators isoliert, sodass wir nur diese Liste durchlaufen müssen).

Es ist auch ganz einfach, z. B. die Benutzernamen derjenigen anzuzeigen, die Optionen für das Filterelement mit der ID 1 erstellt haben:

entities
  .filterItems[1].options
  .map(id => entities.options[id])
  .map(option => entities.optionCreators[option.created_by].username)

2) Es scheint, dass der objektschlüsselgesteuerte Ansatz es schwieriger macht, mit Standard-JSON-Ein- / Ausgängen für eine API umzugehen. (Deshalb habe ich mich in erster Linie für das Array von Objekten entschieden.) Wenn Sie sich also für diesen Ansatz entscheiden, verwenden Sie einfach eine Funktion, um sie zwischen dem JSON-Format und dem Statusformformat hin und her zu transformieren? Das scheint klobig. (Wenn Sie diesen Ansatz befürworten, ist dies Teil Ihrer Argumentation, dass dies weniger klobig ist als der oben beschriebene Reduzierer für Objektarrays?)

Eine JSON-Antwort kann mit zB normalizr normalisiert werden .

3) Ich weiß, dass Dan Abramov Redux so entworfen hat, dass es theoretisch unabhängig von Zustandsdatenstrukturen ist (wie in "Konventionell ist der Zustand der obersten Ebene ein Objekt oder eine andere Schlüsselwertsammlung wie eine Karte, aber technisch kann es jede sein." Typ, "Betonung meiner). Aber angesichts der obigen Ausführungen ist es nur "empfohlen", ein Objekt mit ID zu versehen, oder gibt es andere unvorhergesehene Schwachstellen, auf die ich stoßen werde, wenn ich eine Reihe von Objekten verwende, die es so machen, dass ich das einfach abbrechen sollte planen und versuchen, bei einem Objekt zu bleiben, das mit einem ID versehen ist?

Dies ist wahrscheinlich eine Empfehlung für komplexere Apps mit vielen tief verschachtelten API-Antworten. In Ihrem speziellen Beispiel spielt es jedoch keine Rolle.

Tobiasandersen
quelle
1
mapGibt wie hier undefiniert zurück , wenn Ressourcen separat abgerufen werden, was filters viel zu kompliziert macht. Gibt es eine Lösung?
Saravanabalagi Ramachandran
1
@tobiasandersen Halten Sie es für in Ordnung, dass der Server normalisierte Daten zurückgibt, die sich ideal für React / Redux eignen, um zu vermeiden, dass der Client die Konvertierung über Bibliotheken wie normalizr durchführt? Mit anderen Worten, lassen Sie den Server die Daten und nicht den Client normalisieren.
Matthew