Redux: Wie schneide ich die Boilerplate beim Aktualisieren verschachtelter Entitäten?

Erstellt am 3. Nov. 2015  ·  32Kommentare  ·  Quelle: reduxjs/redux

Ich habe also diese verschachtelte Struktur unter meinem Status

state = {
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe1'},{title: 'exe2'}]}
   ]
}

Ich versuche, eine Reduzierung zu erstellen, die den vorherigen Status nicht mutiert, aber es kommt zu einem Punkt, an dem ich mehr Zeit damit verbringe, herauszufinden, wie dies zu tun ist, als den Rest der App zu codieren. Es wird ziemlich frustrierend.

Wenn ich beispielsweise eine neue leere Übung hinzufügen oder eine vorhandene aktualisieren und die Daten mutieren möchte, würde ich einfach Folgendes tun:

state.plans[planIdx].exercises.push({})

state.plans[planIdx].exercises[exerciseIdx] = exercise

Aber was könnte mein bester Ansatz sein, um dasselbe in dieser verschachtelten Struktur zu tun? Ich habe die Redux-Dokumente und auch den Teil zur Fehlerbehebung gelesen, aber am weitesten habe ich einen Plan aktualisiert, in dem ich Folgendes tun würde:

case 'UPDATE_PLAN':
    return {
      ...state,
      plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
      ]
    };

Gibt es nicht eine schnellere Möglichkeit, damit zu arbeiten? Selbst wenn ich externe Bibliotheken verwenden muss oder wenn mir jemand erklären kann, wie ich besser damit umgehen kann ...

Vielen Dank!

docs question

Hilfreichster Kommentar

Ja, wir empfehlen, Ihre Daten zu normalisieren.
Auf diese Weise müssen Sie nicht „tief“ gehen: Alle Ihre Entitäten befinden sich auf derselben Ebene.

Ihr Staat würde also so aussehen

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

Ihre Reduzierungen könnten aussehen

import merge from 'lodash/object/merge';

const exercises = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...action.exercise
      }
    };
  case 'UPDATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.exercise
      }
    };
  default:
    if (action.entities && action.entities.exercises) {
      return merge({}, state, action.entities.exercises);
    }
    return state;
  }
}

const plans = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...action.plan
      }
    };
  case 'UPDATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.plan
      }
    };
  default:
    if (action.entities && action.entities.plans) {
      return merge({}, state, action.entities.plans);
    }
    return state;
  }
}

const entities = combineReducers({
  plans,
  exercises
});

const currentPlans = (state = [], action) {
  switch (action.type) {
  case 'CREATE_PLAN':
    return [...state, action.id];
  default:
    return state;
  }
}

const reducer = combineReducers({
  entities,
  currentPlans
});

Also, was ist hier los? Beachten Sie zunächst, dass der Zustand normalisiert ist. Wir haben niemals Entitäten in anderen Entitäten. Stattdessen verweisen sie durch IDs aufeinander. Wenn sich also ein Objekt ändert, gibt es nur einen einzigen Ort, an dem es aktualisiert werden muss.

Zweitens beachten Sie, wie wir auf CREATE_PLAN reagieren, indem wir beide eine entsprechende Entität zum plans -Reduzierer hinzufügen _und_, indem wir ihre ID zum currentPlans -Reduzierer hinzufügen. Das ist wichtig. In komplexeren Apps können Beziehungen bestehen, z. B. kann plans Reducer ADD_EXERCISE_TO_PLAN auf die gleiche Weise behandeln, indem eine neue ID an das Array im Plan angehängt wird. Wenn die Übung selbst aktualisiert wird, muss der plans -Reduzierer dies nicht wissen, da sich die ID nicht geändert hat.

Drittens beachten Sie, dass die Entitätsreduzierer ( plans und exercises ) spezielle Klauseln haben, die auf action.entities . Dies ist der Fall, wenn wir eine Serverantwort mit "bekannter Wahrheit" haben, die wir alle unsere Entitäten aktualisieren möchten, um sie widerzuspiegeln. Um Ihre Daten auf diese Weise vorzubereiten, bevor Sie eine Aktion auslösen, können Sie normalizr verwenden . Sie können es im Beispiel "reale Welt" in Redux Repo sehen.

Beachten Sie schließlich, wie ähnlich die Reduzierungen von Entitäten sind. Möglicherweise möchten Sie eine Funktion schreiben, um diese zu generieren. Es liegt außerhalb des Rahmens meiner Antwort - manchmal möchten Sie mehr Flexibilität und manchmal möchten Sie weniger Boilerplate. Sie können den Paginierungscode in Beispielreduzierern aus der „realen Welt“ auschecken, um ein Beispiel für das Generieren ähnlicher Reduzierungen zu erhalten.

Oh, und ich habe die Syntax { ...a, ...b } . Es ist in Babel Stufe 2 als ES7-Vorschlag aktiviert. Es heißt "Object Spread Operator" und entspricht dem Schreiben von Object.assign({}, a, b) .

Für Bibliotheken können Sie Lodash (achten Sie jedoch darauf, dass Sie nicht mutieren, z. B. merge({}, a, b} ist korrekt, aber merge(a, b) nicht), Updeep , -Addons-Update oder etwas anderes verwenden. Wenn Sie jedoch tiefgreifende Aktualisierungen vornehmen müssen, bedeutet dies wahrscheinlich, dass Ihr Statusbaum nicht flach genug ist und Sie die funktionale Zusammensetzung nicht ausreichend nutzen. Schon dein erstes Beispiel:

case 'UPDATE_PLAN':
  return {
    ...state,
    plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
    ]
  };

kann geschrieben werden als

const plan = (state = {}, action) => {
  switch (action.type) {
  case 'UPDATE_PLAN':
    return Object.assign({}, state, action.plan);
  default:
    return state;
  }
}

const plans = (state = [], action) => {
  if (typeof action.idx === 'undefined') {
    return state;
  }
  return [
    ...state.slice(0, action.idx),
    plan(state[action.idx], action),
    ...state.slice(action.idx + 1)
  ];
};

// somewhere
case 'UPDATE_PLAN':
  return {
    ...state,
    plans: plans(state.plans, action)
  };

Alle 32 Kommentare

Es wird empfohlen, verschachtelte JSON wie folgt zu normalisieren:

Ja, wir empfehlen, Ihre Daten zu normalisieren.
Auf diese Weise müssen Sie nicht „tief“ gehen: Alle Ihre Entitäten befinden sich auf derselben Ebene.

Ihr Staat würde also so aussehen

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

Ihre Reduzierungen könnten aussehen

import merge from 'lodash/object/merge';

const exercises = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...action.exercise
      }
    };
  case 'UPDATE_EXERCISE':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.exercise
      }
    };
  default:
    if (action.entities && action.entities.exercises) {
      return merge({}, state, action.entities.exercises);
    }
    return state;
  }
}

const plans = (state = {}, action) => {
  switch (action.type) {
  case 'CREATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...action.plan
      }
    };
  case 'UPDATE_PLAN':
    return {
      ...state,
      [action.id]: {
        ...state[action.id],
        ...action.plan
      }
    };
  default:
    if (action.entities && action.entities.plans) {
      return merge({}, state, action.entities.plans);
    }
    return state;
  }
}

const entities = combineReducers({
  plans,
  exercises
});

const currentPlans = (state = [], action) {
  switch (action.type) {
  case 'CREATE_PLAN':
    return [...state, action.id];
  default:
    return state;
  }
}

const reducer = combineReducers({
  entities,
  currentPlans
});

Also, was ist hier los? Beachten Sie zunächst, dass der Zustand normalisiert ist. Wir haben niemals Entitäten in anderen Entitäten. Stattdessen verweisen sie durch IDs aufeinander. Wenn sich also ein Objekt ändert, gibt es nur einen einzigen Ort, an dem es aktualisiert werden muss.

Zweitens beachten Sie, wie wir auf CREATE_PLAN reagieren, indem wir beide eine entsprechende Entität zum plans -Reduzierer hinzufügen _und_, indem wir ihre ID zum currentPlans -Reduzierer hinzufügen. Das ist wichtig. In komplexeren Apps können Beziehungen bestehen, z. B. kann plans Reducer ADD_EXERCISE_TO_PLAN auf die gleiche Weise behandeln, indem eine neue ID an das Array im Plan angehängt wird. Wenn die Übung selbst aktualisiert wird, muss der plans -Reduzierer dies nicht wissen, da sich die ID nicht geändert hat.

Drittens beachten Sie, dass die Entitätsreduzierer ( plans und exercises ) spezielle Klauseln haben, die auf action.entities . Dies ist der Fall, wenn wir eine Serverantwort mit "bekannter Wahrheit" haben, die wir alle unsere Entitäten aktualisieren möchten, um sie widerzuspiegeln. Um Ihre Daten auf diese Weise vorzubereiten, bevor Sie eine Aktion auslösen, können Sie normalizr verwenden . Sie können es im Beispiel "reale Welt" in Redux Repo sehen.

Beachten Sie schließlich, wie ähnlich die Reduzierungen von Entitäten sind. Möglicherweise möchten Sie eine Funktion schreiben, um diese zu generieren. Es liegt außerhalb des Rahmens meiner Antwort - manchmal möchten Sie mehr Flexibilität und manchmal möchten Sie weniger Boilerplate. Sie können den Paginierungscode in Beispielreduzierern aus der „realen Welt“ auschecken, um ein Beispiel für das Generieren ähnlicher Reduzierungen zu erhalten.

Oh, und ich habe die Syntax { ...a, ...b } . Es ist in Babel Stufe 2 als ES7-Vorschlag aktiviert. Es heißt "Object Spread Operator" und entspricht dem Schreiben von Object.assign({}, a, b) .

Für Bibliotheken können Sie Lodash (achten Sie jedoch darauf, dass Sie nicht mutieren, z. B. merge({}, a, b} ist korrekt, aber merge(a, b) nicht), Updeep , -Addons-Update oder etwas anderes verwenden. Wenn Sie jedoch tiefgreifende Aktualisierungen vornehmen müssen, bedeutet dies wahrscheinlich, dass Ihr Statusbaum nicht flach genug ist und Sie die funktionale Zusammensetzung nicht ausreichend nutzen. Schon dein erstes Beispiel:

case 'UPDATE_PLAN':
  return {
    ...state,
    plans: [
      ...state.plans.slice(0, action.idx),
      Object.assign({}, state.plans[action.idx], action.plan),
      ...state.plans.slice(action.idx + 1)
    ]
  };

kann geschrieben werden als

const plan = (state = {}, action) => {
  switch (action.type) {
  case 'UPDATE_PLAN':
    return Object.assign({}, state, action.plan);
  default:
    return state;
  }
}

const plans = (state = [], action) => {
  if (typeof action.idx === 'undefined') {
    return state;
  }
  return [
    ...state.slice(0, action.idx),
    plan(state[action.idx], action),
    ...state.slice(action.idx + 1)
  ];
};

// somewhere
case 'UPDATE_PLAN':
  return {
    ...state,
    plans: plans(state.plans, action)
  };

Es wäre schön, daraus ein Rezept zu machen.

Idealerweise möchten wir ein Beispiel für ein Kanban-Board.
Es ist perfekt für verschachtelte Entitäten, da in "Spuren" möglicherweise "Karten" enthalten sind.

@ andre0799 oder Sie könnten einfach Immutable.js verwenden))

Idealerweise möchten wir ein Beispiel für ein Kanban-Board.

Ich habe einen geschrieben . Vielleicht könnten Sie es gabeln und nach Ihren Wünschen anpassen.

Immutable.js ist nicht immer eine gute Lösung. Es berechnet den Hash jedes übergeordneten Statusknotens neu, beginnend mit dem von Ihnen geänderten Knoten, und dies wird in bestimmten Fällen zu einem Engpass (dies ist jedoch nicht sehr häufig). Idealerweise sollten Sie einige Benchmarks festlegen, bevor Sie Immutable.js in Ihre Anwendung integrieren.

Danke @gaearon für deine Antwort, tolle Erklärung!

Wenn Sie also CREATE_PLAN ausführen, sollten Sie automatisch eine Standardübung erstellen und diese hinzufügen. Wie soll ich mit solchen Fällen umgehen? Soll ich dann 3 Aktionen hintereinander aufrufen? CREATE_PLAN, CREATE_EXERCISE, ADD_EXERCISE_TO_PLAN Woher soll ich diese Anrufe tätigen, wenn dies der Fall ist?

Wenn Sie also CREATE_PLAN ausführen, sollten Sie automatisch eine Standardübung erstellen und diese hinzufügen. Wie soll ich mit solchen Fällen umgehen?

Während ich im Allgemeinen für viele Reduzierer bin, die dieselbe Aktion ausführen, kann dies für Entitäten mit Beziehungen zu kompliziert werden. In der Tat schlage ich vor, diese als separate Aktionen zu modellieren.

Mit der Redux Thunk- Middleware können Sie einen Aktionsersteller schreiben, der beide aufruft:

function createPlan(title) {
  return dispatch => {
    const planId = uuid();
    const exerciseId = uuid();

    dispatch({
      type: 'CREATE_EXERCISE',
      id: exerciseId,
      exercise: {
        id: exerciseId,
        title: 'Default'
      }
    });

    dispatch({
      type: 'CREATE_PLAN',
      id: planId,
      plan: {
        id: planId,
        exercises: [exerciseId],
        title
      }
    });
  };
}

Wenn Sie dann die Redux Thunk-Middleware anwenden, können Sie sie normal aufrufen:

store.dispatch(createPlan(title));

Nehmen wir also an, ich habe irgendwo im Backend einen Post-Editor mit solchen Beziehungen (Posts, Autoren, Tags, Anhänge usw.).

Wie gehe ich vor, um currentPosts ähnlich dem currentPlans -Schlüsselarray anzuzeigen? Müsste ich jeden Schlüssel in currentPosts dem entsprechenden Objekt in entities.posts in der Funktion mapStateToProps zuordnen? Wie wäre es mit dem Sortieren von currentPosts ?

Gehört das alles zu einer Reduzierzusammensetzung?

Mir fehlt hier etwas ...

In Bezug auf die ursprüngliche Frage glaube ich, dass React Immutability Helpers zu diesem Zweck erstellt wurden

Wie gehe ich vor, um currentPosts anzuzeigen, die dem currentPlans-Schlüsselarray ähneln? Muss ich jeden Schlüssel in currentPosts seinem entsprechenden Objekt in entity.posts in der Funktion mapStateToProps zuordnen? Wie wäre es mit dem Sortieren von currentPosts?

Das ist richtig. Sie würden alles tun, wenn Sie die Daten abrufen. Bitte konsultieren Sie die Beispiele "Einkaufswagen" und "reale Welt", die mit Redux Repo geliefert werden.

Vielen Dank, ich hatte bereits eine Idee, nachdem ich Computing Derived Data in den Dokumenten gelesen hatte.

Ich werde diese Beispiele noch einmal überprüfen. Ich habe wahrscheinlich nicht viel verstanden, was zuerst los war, als ich sie las.

@ andre0799

In jede connect() ed-Komponente wird standardmäßig dispatch als Requisite injiziert.

this.props.dispatch(createPlan(title));

Dies ist eine Verwendungsfrage, die nichts mit diesem Thread zu tun hat. Es ist besser, Beispiele zu konsultieren oder StackOverflow-Fragen zu erstellen.

Ich stimme Dan darin zu, die Daten zu normalisieren und die staatliche Struktur so weit wie möglich zu reduzieren. Es kann als Rezept / Best Practice in die Dokumentation aufgenommen werden, da es mir einige Kopfschmerzen erspart hätte.

Da ich den Fehler gemacht habe, ein wenig Tiefe in meinem Zustand zu haben, habe ich diese Bibliothek erstellt, um das Nachrüsten und Verwalten des Tiefenzustands mit Redux zu erleichtern: https://github.com/baptistemanson/immutable-path
Vielleicht habe ich alles falsch verstanden, aber Ihr Feedback würde mich interessieren. Hoffe es wird jemandem helfen.

Das war hilfreich. Dank an alle.

Wie würden Sie einem Plan eine Übung hinzufügen, die dieselbe Struktur wie das Beispiel aus der Praxis verwendet? Angenommen, das Hinzufügen einer Übung hat die neu erstellte Übungsentität mit einem planId -Feld zurückgegeben. Ist es möglich, die neue Übung zu diesem Plan hinzuzufügen, ohne einen Reduzierer für Pläne schreiben zu müssen, und sich speziell eine Aktion CREATE_EXERCISE anzuhören?

Tolle Diskussion und Information hier. Ich möchte normalizr für mein Projekt verwenden, habe jedoch eine Frage zum Speichern der aktualisierten Daten auf einem Remote-Server. Gibt es hauptsächlich eine einfache Möglichkeit, die normalisierte Form nach der Aktualisierung wieder in die verschachtelte Form zurückzusetzen, die von der Remote-API bereitgestellt wird? Dies ist wichtig, wenn der Client Änderungen vornimmt und diese an die Remote-API zurückmelden muss, wo er keine Kontrolle über die Form der Aktualisierungsanforderung hat.

Beispiel: Der Client ruft die verschachtelten Übungsdaten ab -> Der Client normalisiert sie und speichert sie im Redux -> Der Benutzer nimmt Änderungen an den normalisierten Daten auf der Clientseite vor -> Der Benutzer klickt auf Speichern -> Die Client-App wandelt die aktualisierten normalisierten Daten wieder in die verschachtelte Form um so kann es es an den Remote-Server senden -> Client sendet an Server

Wenn ich normalizr verwenden würde, müsste ich einen benutzerdefinierten Transformator für den fettgedruckten Schritt schreiben, oder gibt es eine Bibliothek oder eine Hilfsmethode, die Sie dafür empfehlen würden? Alle Empfehlungen wäre sehr dankbar.

Vielen Dank

Es gibt etwas namens https://github.com/gpbl/denormalizr, aber ich bin mir nicht sicher, wie genau es normaleizr-Updates verfolgt. Ich habe in ein paar Stunden normalizr für die App geschrieben, an der ich gearbeitet habe. Sie können sie gerne teilen und die Denormalisierung hinzufügen 😄.

Cool Ich werde auf jeden Fall einen Blick auf die Denormalisierung werfen und zu Ihrem Projekt beitragen, sobald ich etwas habe. Tolle Arbeit für ein paar Stunden ;-) Danke, dass du dich bei mir gemeldet hast.

Ich bin in der gleichen Situation wie beim Ändern tief verschachtelter Daten. Ich habe jedoch eine funktionierende Lösung mit immutable.js gefunden.

Ist es in Ordnung, wenn ich auf einen StackOverflow-Beitrag verweise, den ich hier erstellt habe, um Feedback zur Lösung zu erhalten?

Ich verlinke es vorerst. Bitte lösche meinen Beitrag oder sage, wenn es unangemessen ist, hier zu verlinken:
http://stackoverflow.com/questions/37171203/manipulating-data-in-nested-arrays-in-redux-with-immutable-js

Ich habe gesehen, dass dieser Ansatz in der Vergangenheit empfohlen wurde. Dieser Ansatz scheint jedoch nicht gut zu funktionieren, wenn ein verschachteltes Objekt entfernt werden muss. In diesem Fall müsste der Reduzierer alle Verweise auf das Objekt durchsehen und entfernen, eine Operation, die O (n) wäre, bevor er das Objekt selbst entfernen kann. Ist jemand auf ein ähnliches Problem gestoßen und hat es gelöst?

@ariofrio : ah ... ich bin verwirrt. Der Punkt der Normalisierung ist, dass die Objekte nicht verschachtelt gespeichert sind und es nur einen Verweis auf ein bestimmtes Element gibt, was das Aktualisieren dieses Elements vereinfacht. Wenn es nun mehrere andere Entitäten gibt, die dieses Element anhand der ID "referenziert" haben, müssen sie ebenfalls aktualisiert werden, so als ob die Dinge nicht normalisiert wären.

Gibt es ein bestimmtes Problem oder ein problematisches Szenario, mit dem Sie sich befassen?

Hier ist was ich meine. Angenommen, der aktuelle Status sieht folgendermaßen aus:

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 6]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
      5: {title: 'exe5'}
      6: {title: 'exe6'}
    }
  },
  currentPlans: [1, 2]
}

In diesem Beispiel kann jede Übung nur von einem Plan referenziert werden. Wenn der Benutzer auf "Übung entfernen" klickt, sieht die Nachricht möglicherweise folgendermaßen aus:

{type: "REMOVE_EXERCISE", payload: 2}

Um dies jedoch richtig zu implementieren, müsste man alle Pläne und dann alle Übungen in jedem Plan durchlaufen, um denjenigen zu finden, der auf die Übung mit ID 2 verwiesen hat, um eine baumelnde Referenz zu vermeiden. Dies ist die O (n) -Operation, über die ich mir Sorgen gemacht habe.

Eine Möglichkeit, dies zu vermeiden, besteht darin, die Plan-ID in die Nutzdaten von REMOVE_EXERCISE aufzunehmen. Derzeit sehe ich jedoch keinen Vorteil gegenüber der Verwendung der Verschachtelung der Strukturen. Wenn wir stattdessen einen verschachtelten Status verwenden, sieht der Status möglicherweise folgendermaßen aus:

{
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe6'}]}
   ]
}

Und die Meldung zum Entfernen der Übung könnte folgendermaßen aussehen:

{type: "REMOVE_EXERCISE", payload: {plan_index: 0, exercise_index: 1}}

Ein paar Gedanken:

  • Sie können eine umgekehrte Suche nach Übungen durchführen, um dieses Szenario zu vereinfachen. In ähnlicher Weise generiert die Redux-ORM-Bibliothek automatisch "Durch-Tabellen" für Beziehungen mit vielen Typen. In diesem Fall hätten Sie also eine "PlanExercise" -Tabelle in Ihrem Geschäft, die {id, planId, exerciseId} Triplets enthalten würde. Sicherlich ein O (n) -Scan, aber ein unkomplizierter.
  • Eine O (n) -Operation ist von Natur aus keine schlechte Sache. Das hängt ganz davon ab, wie groß N ist, der konstante Faktor vor dem Begriff, wie oft es passiert und was sonst noch in Ihrer Anwendung vor sich geht. Das Durchlaufen einer Liste von 10 oder 15 Elementen und das Durchführen einiger Gleichheitsprüfungen bei einem Klick auf eine Benutzertaste ist eine völlig andere Sache als beispielsweise das Durchlaufen einer Liste von 10 Millionen Elementen, die alle 500 ms in das System eingehen und für jedes eine teure Operation ausführen Artikel. In diesem Fall besteht die Wahrscheinlichkeit, dass selbst das Durchsehen von Tausenden von Plänen keinen bedeutenden Engpass darstellt.
  • Ist dies ein tatsächliches Leistungsproblem, das Sie sehen, oder blicken Sie nur auf mögliche theoretische Probleme?

Letztendlich sind sowohl verschachtelte als auch normalisierte Zustände Konventionen. Es gibt gute Gründe, den normalisierten Status mit Redux zu verwenden, und es kann gute Gründe geben, Ihren Status normal zu halten. Wählen Sie, was für Sie funktioniert :)

meine Lösung wie folgt:

function deepCombinReducer(parentReducer, subReducer) {
    return function (state = parentReducer(void(0) /* get parent reducer initial state */, {}) {
        let finalState = {...state};

        for (var k in subReducer) {
          finalState[k] = subReducer(state[k], action);
        }

       return parentReducer(finalState, action);
    };
}

const parentReducer = function(state = {}, action) {
    return state;
}

const subReducer = function(state = [], action) {
    state = Immutable.fromJS(state).toJS();
    switch(action.type) {
       case 'ADD':
          state.push(action.sub);
           return state;
       default:
          return state;
   }
}

export default combineReducers({
   parent: deepCombinReducer(parentReducer, {
       sub: subReducer
   })
})

Dann können Sie den Laden wie folgt erhalten:

{
    parent: {
       sub: []
    }
}

dispatch({
    type: 'ADD',
    sub: '123'
});

// the store will change to:
{
    parent: {
       sub: ['123']
    }
}

@smashercosmo immutable.js mit tief verschachteltem Zustand? Ich bin gespannt wie

@gaearon

Wir haben niemals Entitäten in anderen Entitäten.

Ich verstehe es nicht Wir haben hier mindestens drei Verschachtelungsebenen:

{
  entities: {
    plans: {
      1: {title: 'A', exercises: [1, 2, 3]},
      2: {title: 'B', exercises: [5, 1, 2]}
     },
    exercises: {
      1: {title: 'exe1'},
      2: {title: 'exe2'},
      3: {title: 'exe3'}
    }
  },
  currentPlans: [1, 2]
}

entities.plans[1] - three levels
entities.exercises[1] - three levels

Dies ist ein nicht verschachteltes Objekt. Nur eine Ebene.

{
   plans: [1,2, 3],
   exercises: [1,2,3],
   'so forth': [1,2,3]
}

@wzup : Zu

Die Bedeutung von "Verschachteln" ist hier, wenn die Daten selbst verschachtelt sind, wie in diesem Beispiel von früher im Thread:

{
   plans: [
    {title: 'A', exercises: [{title: 'exe1'}, {title: 'exe2'},{title: 'exe3'}]},
    {title: 'B', exercises: [{title: 'exe5'}, {title: 'exe6'}]}
   ]
}

In diesem Beispiel besteht die einzige Möglichkeit, auf Übung "exe6" zuzugreifen, darin, sich in die Struktur wie plans[1].exercises[2] vertiefen.

Ich interessiere mich für die Frage von @tmonte :

Wie würden Sie einem Plan eine Übung hinzufügen, die dieselbe Struktur wie das Beispiel aus der Praxis verwendet? Angenommen, das Hinzufügen einer Übung hat die neu erstellte Übungsentität mit einem planId-Feld zurückgegeben. Ist es möglich, die neue Übung zu diesem Plan hinzuzufügen, ohne einen Reduzierer für Pläne schreiben zu müssen, und eine Aktion CREATE_EXERCISE speziell anzuhören?

Wenn Sie viele Entitäten haben, kann es schmerzhaft sein, für jede Entität einen Reduzierer zu erstellen, aber dieser Ansatz könnte es lösen. Ich habe bisher keine Lösung dafür gefunden.

Ich verwende normalerweise mergeWith anstelle von merge für mehr Flexibilität:

import mergeWith from 'lodash/mergeWith';

// Updates an entity cache in response to any action with `entities`.
function entities(state = {}, action) {
  // Here where we STORE or UPDATE one or many entities
  // So check if the action contains the format we will manage
  // wich is `payload.entities`
  if (action.payload && action.payload.entities) {
    // if the entity is already in the store replace
    // it with the new one and do not merge. Why?
    // Assuming we have this product in the store:
    //
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //     rateCategory: 1234
    //   }
    // }
    //
    // We will updated with
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //   }
    // }
    //
    // The result if we were using `lodash/merge`
    // notice the rate `rateCategory` hasn't changed:
    // products: {
    //   1: {
    //     id: 1,
    //     name: 'Awesome product name',
    //     rateCategory: 1234
    //   }
    // }
    // for this particular use case it's safer to use
    // `lodash/mergeWith` and skip the merge
    return mergeWith({}, state, action.payload.entities, (oldD, newD) => {
      if (oldD && oldD.id && oldD.id === newD.id) {
        return newD;
      }
      return undefined;
    });
  }

  // Here you could register other handlers to manipulate 
  // the entities
  switch (action.type) {
    case ActionTypes.SOME_ACTION:
      // do something;
    default:
      return state;
  }
}

const rootReducer = combineReducers({
  entities,
});
export default rootReducer;
War diese Seite hilfreich?
0 / 5 - 0 Bewertungen