processstrategy javascript reactjs redux normalizr

javascript - normalizr processstrategy



¿Cómo se agrega/elimina a una tienda redux generada con normalizr? (4)

Mirando los ejemplos del README :

Dada la estructura "mala":

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

Es extremadamente fácil agregar un nuevo objeto. Todo lo que tengo que hacer es algo como

return { ...state, myNewObject }

En el reductor.

Ahora, dada la estructura del árbol "bueno", no tengo idea de cómo abordarlo.

{ 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'' } } } }

Cada enfoque en el que he pensado requiere una manipulación compleja de objetos, lo que me hace sentir que no estoy en el camino correcto porque se supone que normalizr me está haciendo la vida más fácil.

No puedo encontrar ningún ejemplo en línea de alguien que trabaje con el árbol normalizr de esta manera. El ejemplo oficial no agrega ni elimina, por lo que tampoco fue de ayuda.

¿Podría alguien decirme cómo agregar / eliminar de un árbol normalizr de la manera correcta?


En tu reductor, conserva una copia de los datos no normalizados. De esta forma, puede hacer algo como esto (al agregar un nuevo objeto a una matriz en estado):

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

Si no desea mantener datos no normalizados en su tienda, también puede utilizar la denormalize


Implementé una pequeña desviación de un reductor genérico que se puede encontrar a través de Internet. Es capaz de eliminar elementos de la memoria caché. Todo lo que tiene que hacer es asegurarse de que en cada eliminación envíe una acción con el campo eliminado:

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 }

ejemplo de uso en código de acción:

await AlarmApi.remove(alarmId) dispatch({ type: ''ALARM_DELETED'', alarmId, deleted: {alarms: [alarmId]}, })


La mayoría de las veces utilizo normalizr para los datos que obtengo de una API, porque no tengo ningún control sobre las (generalmente) estructuras de datos anidados profundos. Vamos a diferenciar Entidades y Resultado y su uso.

Entidades

Todos los datos puros se encuentran en el objeto de entidades después de que se haya normalizado (en su caso, articles y users ). Yo recomendaría usar un reductor para todas las entidades o un reductor para cada tipo de entidad. El (los) reductor (es) de la entidad deberían ser responsables de mantener sincronizados sus datos (servidor) y de tener una única fuente de verdad.

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

Resultado

Los resultados son solo referencias a tus entidades. Imagine el siguiente escenario: (1) obtiene de articles recomendados por API con ids: [''1'', ''2''] . Guardas las entidades en tu artículo entidad reductora . (2) Ahora busca todos los artículos escritos por un autor específico con id: ''X'' . De nuevo, sincroniza los artículos en el reductor de entidades del artículo . El reductor de entidad de artículo es la única fuente de verdad para todos los datos de su artículo, eso es todo. Ahora desea tener otro lugar para diferenciar los artículos ((1) artículos recomendados y (2) artículos por autor X). Puede guardarlos fácilmente en otro reductor específico de caso de uso. El estado de ese reductor podría verse así:

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

Ahora puede ver fácilmente que el artículo del autor X también es un artículo recomendado. Pero mantienes solo una fuente única de verdad en el reductor de entidad de tu artículo.

En su componente, puede asignar entidades + recommended / articlesByAuthor para presentar la entidad.

Descargo de responsabilidad: Puedo recomendar una publicación de blog que escribí, que muestra cómo una aplicación del mundo real usa el normalizr para evitar problemas en la administración del estado: Redux Normalizr: mejore su administración estatal


Lo siguiente es directamente de una publicación del creador redux / normalizr here :

Entonces su estado se vería así:

{ 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] }

Sus reductores pueden parecerse

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

Entonces, ¿qué está pasando aquí? Primero, tenga en cuenta que el estado está normalizado. Nunca tenemos entidades dentro de otras entidades. En cambio, se refieren el uno al otro por ID. Entonces, cada vez que un objeto cambia, solo hay un lugar donde necesita ser actualizado.

En segundo lugar, observe cómo reaccionamos a CREATE_PLAN agregando una entidad apropiada en el reductor de planes y agregando su ID al reductor de planes actual. Esto es importante. En aplicaciones más complejas, puede tener relaciones, por ejemplo, los planes reductores pueden manejar ADD_EXERCISE_TO_PLAN de la misma manera al agregar una nueva ID a la matriz dentro del plan. Pero si el ejercicio en sí se actualiza, no hay necesidad de que el reductor de planes lo sepa, ya que la identificación no ha cambiado.

En tercer lugar, observe que las entidades reductoras (planes y ejercicios) tienen cláusulas especiales que vigilan las entidades de acción. Esto es en caso de que tengamos una respuesta del servidor con "verdad conocida" que queremos actualizar todas nuestras entidades para reflejar. Para preparar sus datos de esta manera antes de enviar una acción, puede usar normalizr. Puede verlo en el ejemplo del "mundo real" en el repositorio Redux.

Finalmente, observe cómo las entidades reductoras son similares. Es posible que desee escribir una función para generar esos. Está fuera del alcance de mi respuesta: a veces quieres más flexibilidad, y a veces quieres menos repetitivo. Puede consultar el código de paginación en reductores de ejemplo del "mundo real" para obtener un ejemplo de generación de reductores similares.

Ah, y utilicé la sintaxis {... a, ... b}. Está habilitado en la etapa 2 de Babel como propuesta de ES7. Se llama "operador de propagación de objetos" y es equivalente a escribir Object.assign ({}, a, b).

En cuanto a las bibliotecas, puede usar Lodash (tenga cuidado de no mutar, por ejemplo, fusionar ({}, a, b} es correcto, pero fusionar (a, b) no lo es), avanzar, reaccionar-agregar-actualizar o algo más. Sin embargo, si encuentra que necesita hacer actualizaciones profundas, probablemente significa que su árbol de estado no es lo suficientemente plano, y que no utiliza la composición funcional suficiente. Incluso su primer ejemplo:

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

Se puede escribir como

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