web-dev-qa-db-de.com

Wie füge ich einen mit normalizr generierten Redux-Store hinzu oder entferne ihn?

Schauen Sie sich die Beispiele aus der README an:

Angesichts der "schlechten" Struktur:

[{
  id: 1,
  title: 'Some Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}, {
  id: 2,
  title: 'Other Article',
  author: {
    id: 1,
    name: 'Dan'
  }
}]

Es ist sehr einfach, ein neues Objekt hinzuzufügen. Alles was ich tun muss ist etwas 

return {
  ...state,
  myNewObject
}

Im Reduzierer.

Nun, angesichts der Struktur des "guten" Baumes, habe ich keine Ahnung, wie ich an ihn herangehen soll.

{
  result: [1, 2],
  entities: {
    articles: {
      1: {
        id: 1,
        title: 'Some Article',
        author: 1
      },
      2: {
        id: 2,
        title: 'Other Article',
        author: 1
      }
    },
    users: {
      1: {
        id: 1,
        name: 'Dan'
      }
    }
  }
}

Jede Herangehensweise, an die ich gedacht habe, erfordert einige komplexe Objektmanipulationen, so dass ich das Gefühl habe, nicht auf dem richtigen Weg zu sein, weil Normalizr mein Leben erleichtern soll.

Ich kann online keine Beispiele für jemanden finden, der auf diese Weise mit dem normalizr-Baum arbeitet. Das offizielle Beispiel macht kein Hinzufügen und Entfernen, daher war es auch keine Hilfe.

Könnte jemand mich wissen lassen, wie ich ihn auf die richtige Art und Weise zu einem normalizr-Baum hinzufügen/entfernen kann?

32
m0meni

Folgendes ist direkt aus einem Beitrag des Redux/Normalizr-Erstellers hier :

So würde Ihr Zustand 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. Sie beziehen sich stattdessen durch IDs aufeinander. Immer wenn sich ein Objekt ändert, gibt es nur eine Stelle, an der es aktualisiert werden muss.

Beachten Sie zweitens, wie wir auf CREATE_PLAN reagieren, indem Sie sowohl eine geeignete Entität im Planreduzierer als auch dessen ID zum aktuellen Plansreduzierer hinzufügen. Das ist wichtig. In komplexeren Apps können Sie Beziehungen haben, z. Planreduzierer kann ADD_EXERCISE_TO_PLAN auf dieselbe Weise behandeln, indem eine neue ID an das Array im Plan angehängt wird. Wenn jedoch die Übung selbst aktualisiert wird, ist es nicht erforderlich, dass der Plan-Reduzierer dies weiß, da sich die ID nicht geändert hat.

Drittens, beachten Sie, dass die Entitätenreduzierer (Pläne und Übungen) spezielle Klauseln enthalten, die auf Handlungsentitäten achten. Dies ist für den Fall, dass wir eine Serverantwort mit der „bekannten Wahrheit“ haben, die wir alle unsere Entitäten aktualisieren möchten, um sie zu reflektieren. Um Ihre Daten auf diese Weise vorzubereiten, bevor Sie eine Aktion absenden, können Sie normalizr verwenden. Sie können es im "realen" Beispiel in Redux Repo sehen.

Beachten Sie schließlich, dass die Entitätenreduzierer ähnlich sind. Möglicherweise möchten Sie eine Funktion schreiben, um diese zu generieren. Es liegt außerhalb meiner Antwort - manchmal möchten Sie mehr Flexibilität und manchmal weniger Heizplatte. Sie können den Paginierungscode in „realen“ Beispielreduzierern überprüfen, um ein Beispiel für die Erzeugung ähnlicher Reduktionskopien zu erhalten.

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

Für Bibliotheken können Sie Lodash verwenden (achten Sie jedoch darauf, nicht zu mutieren, z. B. "merge" ({}, a, b} ist korrekt, aber "merge" (a, b) nicht), updeep, reag-addons-update oder etwas anderes. Wenn Sie jedoch tiefe Aktualisierungen benötigen, bedeutet dies wahrscheinlich, dass Ihr Statusbaum nicht flach genug ist und Sie die funktionale Komposition nicht genug verwenden. Auch das erste 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 als geschrieben werden

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)
  };
28
m0meni

Die meiste Zeit benutze ich normalizr für Daten, die ich von einer API erhalte, weil ich keine Kontrolle über die (normalerweise) tief verschachtelten Datenstrukturen habe. Lassen Sie uns Entities und Result und deren Verwendung unterscheiden.

Entitäten

Alle reinen Daten befinden sich im Entitätsobjekt, nachdem sie normalisiert wurden (in Ihrem Fall articles und users). Ich würde empfehlen, entweder einen Reduzierer für alle Entitäten oder einen Reduzierer für jeden Entitätstyp zu verwenden. Die Entitätsreduzierer sollten dafür verantwortlich sein, Ihre (Server-) Daten synchron zu halten und eine einzige Wahrheitsquelle zu haben.

const initialState = {
  articleEntities: {},
  userEntities: {},
};

Ergebnis

Die Ergebnisse sind nur Verweise auf Ihre Entitäten. Stellen Sie sich das folgende Szenario vor: (1) Sie rufen von einer von der API empfohlenen articles mit ids: ['1', '2']. Sie speichern die Entities in Ihrem article entity reducer. (2) Nun holen Sie alle Artikel eines bestimmten Autors mit id: 'X'. Wieder synchronisieren Sie die Artikel im Artikelentitätsreduzierer. Der Artikelentitätsreduzierer ist die einzige Quelle der Wahrheit für alle Ihre Artikeldaten - das war's. Jetzt möchten Sie einen anderen Ort zur Unterscheidung der Artikel (1) empfohlene Artikel und (2) Artikel nach Autor X) haben. Sie können diese problemlos in einem anderen, anwendungsfallspezifischen Reduzierstück aufbewahren. Der Zustand dieses Reduzierers könnte folgendermaßen aussehen:

const state = {
  recommended: ['1', '2' ],
  articlesByAuthor: {
    X: ['2'],
  },
};

Jetzt können Sie leicht erkennen, dass es sich bei dem Artikel von Autor X ebenfalls um einen empfohlenen Artikel handelt. Sie behalten jedoch nur eine einzige Wahrheitsquelle in Ihrem Artikel-Entitätsreduzierer.

In Ihrer Komponente können Sie einfach Entitäten + recommended/articlesByAuthor zuordnen, um die Entität darzustellen.

Haftungsausschluss: Ich kann einen Blogbeitrag empfehlen, der zeigt, wie eine reale App normalizr verwendet, um Probleme bei der Statusverwaltung zu vermeiden: Redux Normalizr: Verbessern Sie Ihre Statusverwaltung

3
Robin Wieruch

Ich habe eine kleine Abweichung von einem generischen Reduzierer implementiert, der über das Internet gefunden werden kann. Es kann Elemente aus dem Cache löschen. Sie müssen lediglich sicherstellen, dass Sie bei jedem Löschvorgang eine Aktion mit einem gelöschten Feld senden:

export default (state = entities, action) => {
    if (action.response && action.response.entities)
        state = merge(state, action.response.entities)

    if (action.deleted) {
        state = {...state}

        Object.keys(action.deleted).forEach(entity => {
            let deleted = action.deleted[entity]

            state[entity] = Object.keys(state[entity]).filter(key => !deleted.includes(key))
                .reduce((p, id) => ({...p, [id]: state[entity][id]}), {})
        })
    }

    return state
}

anwendungsbeispiel in Aktionscode:

await AlarmApi.remove(alarmId)

dispatch({
    type: 'ALARM_DELETED',
    alarmId,
    deleted: {alarms: [alarmId]},
})
2
dobryy

Behalten Sie in Ihrem Reducer eine Kopie der nicht normalisierten Daten. Auf diese Weise können Sie Folgendes tun (wenn Sie einem Array im Status ein neues Objekt hinzufügen):

case ACTION:
  return {
    unNormalizedData: [...state.unNormalizedData, action.data],
    normalizedData: normalize([...state.unNormalizedData, action.data], normalizrSchema),
  }

Wenn Sie nicht normalisierte Daten in Ihrem Geschäft behalten möchten, können Sie auch denormalize verwenden.

0
Jeff Weinberg