javascript - bootstrap - Combina los reductores de flujo sin añadir anidamiento.
option value html (3)
De acuerdo, aunque el problema ya se resolvió mientras tanto, solo quería compartir la solución que encontré:
import { ActionTypes } from ''redux/lib/createStore''
const mergeReducers = (...reducers) => {
const filter = (state, keys) => (
state !== undefined && keys.length ?
keys.reduce((result, key) => {
result[key] = state[key];
return result;
}, {}) :
state
);
let mapping = null;
return (state, action) => {
if (action && action.type == ActionTypes.INIT) {
// Create the mapping information ..
mapping = reducers.map(
reducer => Object.keys(reducer(undefined, action))
);
}
return reducers.reduce((next, reducer, idx) => {
const filteredState = filter(next, mapping[idx]);
const resultingState = reducer(filteredState, action);
return filteredState !== resultingState ?
{...next, ...resultingState} :
next;
}, state);
};
};
Respuesta anterior:
Para encadenar una serie de reductores, se puede utilizar la siguiente función:
const combineFlat = (reducers) => (state, action) => reducers.reduce((newState, reducer) => reducer(newState, action), state));
Para combinar varios reductores, simplemente utilícelo de la siguiente manera:
const combinedAB = combineFlat([reducerA, reducerB]);
Tengo un escenario donde tengo 2 reductores que son el resultado de un combineReducers
. Quiero combinarlos, pero mantener sus claves al mismo nivel en el anidamiento.
Por ejemplo, dados los siguientes reductores.
const reducerA = combineReducers({ reducerA1, reducerA2 })
const reducerB = combineReducers{{ reducerB1, reducerB2 })
Quiero terminar con una estructura como:
{
reducerA1: ...,
reducerA2: ...,
reducerB1: ...,
reducerB2: ...
}
Si utilizo combineReducers
nuevamente en reducerA
y reducerB
así:
const reducer = combineReducers({ reducerA, reducersB })
Termino con una estructura como:
{
reducerA: {
reducerA1: ...,
reducerA2: ...
},
reducerB: {
reducerB1: ...,
reducerB2: ...
}
}
No puedo combinar el reducerA1
reducerA2
, el reducerB1
reducerA1
, el reducerA2
reducerB1
y el reducerB2
en una sola combineReducers
ya que el reducerA
y el reducerB
reducerA
reducerB
están entregados ya combinados de diferentes paquetes npm.
He intentado usar la biblioteca de reduce-reducers para combinarlos y reducir el estado, una idea que obtuve al ver los documentos de redux , como por ejemplo:
const reducer = reduceReducers(reducerA, reducerB)
Desafortunadamente, esto no funcionó como el reductor resultante de combineReducers
una advertencia si se encuentran claves desconocidas y las ignora al devolver su estado, por lo que la estructura resultante solo contiene la de reducerB
:
{
reducerB1: ...,
reducerB2: ...
}
Realmente no quiero implementar mis propios combineReducers
que no imponen la estructura tan estrictamente si no tengo que hacerlo, por lo que espero que alguien conozca de otra manera, ya sea integrada a redux o desde una biblioteca que pueda Ayúdame con esto. ¿Algunas ideas?
Editar:
Se proporcionó una respuesta (parece que se ha eliminado ahora) que sugirió el uso flat-combine-reducers biblioteca de flat-combine-reducers :
const reducer = flatCombineReducers(reducerA, reducerB)
Esto fue un paso más cerca que los reductores, ya que logró mantener el estado tanto del reducerA
como del reducerA
reducerB
, pero aún se están produciendo los mensajes de advertencia, lo que me hace preguntarme si el estado de fuga que observé antes no era combineReducers
lo combineReducers
de distancia, sino que algo más está sucediendo con la implementación de reductores.
Los mensajes de advertencia son:
Se encontraron claves inesperadas "reducerB1", "reducerB2" en el estado anterior recibido por el reductor. Se espera que encuentre una de las teclas reductoras conocidas en su lugar: "reducerA1", "reducerA2". Las claves inesperadas serán ignoradas.
Se encontraron claves inesperadas "reducerA1", "reducerA2" en el estado anterior recibido por el reductor. Se espera que encuentre una de las teclas reductoras conocidas en su lugar: "reducerB1", "reducerB2". Las claves inesperadas serán ignoradas.
Si hago una compilación de producción, la advertencia desaparece (tal es el camino para muchas advertencias de reacción / redux), pero prefiero que no aparezcan en absoluto.
También he hecho un poco más de búsqueda de otras bibliotecas y he encontrado redux-concatenate-reducers :
const reducer = concatenateReducers([reducerA, reducerB])
Esto tiene el mismo resultado que los reductores de combinación plana por lo que la búsqueda continúa.
Edición 2:
Algunas personas han hecho algunas sugerencias ahora, pero ninguna ha funcionado hasta ahora, así que aquí hay una prueba para ayudar:
import { combineReducers, createStore } from ''redux''
describe(''Sample Tests'', () => {
const reducerA1 = (state = 0) => state
const reducerA2 = (state = { test: "value1"}) => state
const reducerB1 = (state = [ "value" ]) => state
const reducerB2 = (state = { test: "value2"}) => state
const reducerA = combineReducers({ reducerA1, reducerA2 })
const reducerB = combineReducers({ reducerB1, reducerB2 })
const mergeReducers = (...reducers) => (state, action) => {
return /* your attempt goes here */
}
it(''should merge reducers'', () => {
const reducer = mergeReducers(reducerA, reducerB)
const store = createStore(reducer)
const state = store.getState()
const expectedState = {
reducerA1: 0,
reducerA2: {
test: "value1"
},
reducerB1: [ "value" ],
reducerB2: {
test: "value2"
}
}
expect(state).to.deep.equal(expectedState)
})
})
El objetivo es lograr que esta prueba pase Y no produzca ninguna advertencia en la consola.
Edición 3:
Se agregaron más pruebas para cubrir más casos, incluido el manejo de una acción después de la creación inicial y si la tienda se crea con el estado inicial.
import { combineReducers, createStore } from ''redux''
describe(''Sample Tests'', () => {
const reducerA1 = (state = 0) => state
const reducerA2 = (state = { test: "valueA" }) => state
const reducerB1 = (state = [ "value" ]) => state
const reducerB2 = (state = {}, action) => action.type == ''ADD_STATE'' ? { ...state, test: (state.test || "value") + "B" } : state
const reducerA = combineReducers({ reducerA1, reducerA2 })
const reducerB = combineReducers({ reducerB1, reducerB2 })
// from Javaguru''s answer
const mergeReducers = (reducer1, reducer2) => (state, action) => ({
...state,
...reducer1(state, action),
...reducer2(state, action)
})
it(''should merge combined reducers'', () => {
const reducer = mergeReducers(reducerA, reducerB)
const store = createStore(reducer)
const state = store.getState()
const expectedState = {
reducerA1: 0,
reducerA2: {
test: "valueA"
},
reducerB1: [ "value" ],
reducerB2: {}
}
expect(state).to.deep.equal(expectedState)
})
it(''should merge basic reducers'', () => {
const reducer = mergeReducers(reducerA2, reducerB2)
const store = createStore(reducer)
const state = store.getState()
const expectedState = {
test: "valueA"
}
expect(state).to.deep.equal(expectedState)
})
it(''should merge combined reducers and handle actions'', () => {
const reducer = mergeReducers(reducerA, reducerB)
const store = createStore(reducer)
store.dispatch({ type: "ADD_STATE" })
const state = store.getState()
const expectedState = {
reducerA1: 0,
reducerA2: {
test: "valueA"
},
reducerB1: [ "value" ],
reducerB2: {
test: "valueB"
}
}
expect(state).to.deep.equal(expectedState)
})
it(''should merge basic reducers and handle actions'', () => {
const reducer = mergeReducers(reducerA2, reducerB2)
const store = createStore(reducer)
store.dispatch({ type: "ADD_STATE" })
const state = store.getState()
const expectedState = {
test: "valueAB"
}
expect(state).to.deep.equal(expectedState)
})
it(''should merge combined reducers with initial state'', () => {
const reducer = mergeReducers(reducerA, reducerB)
const store = createStore(reducer, { reducerA1: 1, reducerB1: [ "other" ] })
const state = store.getState()
const expectedState = {
reducerA1: 1,
reducerA2: {
test: "valueA"
},
reducerB1: [ "other" ],
reducerB2: {}
}
expect(state).to.deep.equal(expectedState)
})
it(''should merge basic reducers with initial state'', () => {
const reducer = mergeReducers(reducerA2, reducerB2)
const store = createStore(reducer, { test: "valueC" })
const state = store.getState()
const expectedState = {
test: "valueC"
}
expect(state).to.deep.equal(expectedState)
})
it(''should merge combined reducers with initial state and handle actions'', () => {
const reducer = mergeReducers(reducerA, reducerB)
const store = createStore(reducer, { reducerA1: 1, reducerB1: [ "other" ] })
store.dispatch({ type: "ADD_STATE" })
const state = store.getState()
const expectedState = {
reducerA1: 1,
reducerA2: {
test: "valueA"
},
reducerB1: [ "other" ],
reducerB2: {
test: "valueB"
}
}
expect(state).to.deep.equal(expectedState)
})
it(''should merge basic reducers with initial state and handle actions'', () => {
const reducer = mergeReducers(reducerA2, reducerB2)
const store = createStore(reducer, { test: "valueC" })
store.dispatch({ type: "ADD_STATE" })
const state = store.getState()
const expectedState = {
test: "valueCB"
}
expect(state).to.deep.equal(expectedState)
})
})
La implementación de mergeReducers
anterior pasa todas las pruebas, pero aún mergeReducers
advertencias a la consola.
Sample Tests
✓ should merge combined reducers
✓ should merge basic reducers
Unexpected keys "reducerB1", "reducerB2" found in previous state received by the reducer. Expected to find one of the known reducer keys instead: "reducerA1", "reducerA2". Unexpected keys will be ignored.
Unexpected keys "reducerA1", "reducerA2" found in previous state received by the reducer. Expected to find one of the known reducer keys instead: "reducerB1", "reducerB2". Unexpected keys will be ignored.
✓ should merge combined reducers and handle actions
✓ should merge basic reducers and handle actions
✓ should merge combined reducers with initial state
✓ should merge basic reducers with initial state
✓ should merge combined reducers with initial state and handle actions
✓ should merge basic reducers with initial state and handle actions
Es importante tener en cuenta que las advertencias que se están imprimiendo son para el caso de prueba inmediatamente después y que los reductores combineReducers
Reductores solo imprimirán cada advertencia única una vez, por lo tanto, debido a que estoy reutilizando el reductor entre pruebas, las advertencias solo se muestran para el primer caso de prueba. para producirlo (podría combinar los reductores en cada prueba para evitar esto, pero como criterio lo estoy buscando para que no los produzca, estoy contento con esto por ahora).
Si está intentando esto, no me importa si mergeReducers
acepta 2 reductores (como el de arriba), una serie de reductores o un objeto de reductores (como combineReducers
). En realidad, no me importa cómo se logre siempre que no requiera ningún cambio en la creación de reducerA
, reducerB
, reducerA1
, reducerA1
, reducerB1
o reducerB2
.
Edición 4:
Mi solución actual es modificada de la respuesta de Jason Geomaat.
La idea es filtrar el estado que se proporciona al reductor mediante las claves de llamadas anteriores mediante el siguiente contenedor:
export const filteredReducer = (reducer) => {
let knownKeys = Object.keys(reducer(undefined, { type: ''@@FILTER/INIT'' }))
return (state, action) => {
let filteredState = state
if (knownKeys.length && state !== undefined) {
filteredState = knownKeys.reduce((current, key) => {
current[key] = state[key];
return current
}, {})
}
let newState = reducer(filteredState, action)
let nextState = state
if (newState !== filteredState) {
knownKeys = Object.keys(newState)
nextState = {
...state,
...newState
}
}
return nextState;
};
}
Combino el resultado de los reductores filtrados utilizando la biblioteca redux-concatenate-reducers (podría haber usado reductores de combinación plana pero la implementación de la combinación de los primeros parece un poco más robusta). La función mergeReducers
ve así:
const mergeReducers = (...reducers) => concatenateReducers(reducers.map((reducer) => filterReducer(reducer))
Esto se llama así:
const store = createStore(mergeReducers(reducerA, reducerB)
Esto pasa todas las pruebas y no produce ninguna advertencia de los reductores creados con combineReducers
.
Lo único de lo que no estoy seguro es de dónde se va a knownKeys
matriz knownKeys
al llamar al reductor con una acción INIT
. Funciona, pero se siente un poco sucio. Si no hago esto, la única advertencia que se produce es si la tienda se crea con un estado inicial (las teclas adicionales no se filtran al resolver el estado inicial del reductor).
Ok, decidí hacerlo por diversión, no demasiado código ... Esto envolverá un reductor y solo le proporcionará las claves que ha devuelto.
// don''t provide keys to reducers that don''t supply them
const filterReducer = (reducer) => {
let lastState = undefined;
return (state, action) => {
if (lastState === undefined || state == undefined) {
lastState = reducer(state, action);
return lastState;
}
var filteredState = {};
Object.keys(lastState).forEach( (key) => {
filteredState[key] = state[key];
});
var newState = reducer(filteredState, action);
lastState = newState;
return newState;
};
}
En tus pruebas:
const reducerA = filterReducer(combineReducers({ reducerA1, reducerA2 }))
const reducerB = filterReducer(combineReducers({ reducerB1, reducerB2 }))
NOTA: Esto rompe con la idea de que el reductor siempre proporcionará la misma salida con las mismas entradas. Probablemente sería mejor aceptar la lista de claves al crear el reductor:
const filterReducer2 = (reducer, keys) => {
let lastState = undefined;
return (state, action) => {
if (lastState === undefined || state == undefined) {
lastState = reducer(state, action);
return lastState;
}
var filteredState = {};
keys.forEach( (key) => {
filteredState[key] = state[key];
});
return lastState = reducer(filteredState, action);
};
}
const reducerA = filterReducer2(
combineReducers({ reducerA1, reducerA2 }),
[''reducerA1'', ''reducerA2''])
const reducerB = filterReducer2(
combineReducers({ reducerB1, reducerB2 }),
[''reducerB1'', ''reducerB2''])
Solución para los que utilizan Inmutable.
Las soluciones anteriores no manejan las tiendas inmutables, que es lo que necesitaba cuando me topé con esta pregunta. Aquí hay una solución que se me ocurrió, espero que pueda ayudar a alguien más.
import { fromJS, Map } from ''immutable'';
import { combineReducers } from ''redux-immutable'';
const flatCombineReducers = reducers => {
return (previousState, action) => {
if (!previousState) {
return reducers.reduce(
(state = {}, reducer) =>
fromJS({ ...fromJS(state).toJS(), ...reducer(previousState, action).toJS() }),
{},
);
}
const combinedReducers = combineReducers(reducers);
const combinedPreviousState = fromJS(
reducers.reduce(
(accumulatedPreviousStateDictionary, reducer, reducerIndex) => ({
...accumulatedPreviousStateDictionary,
[reducerIndex]: previousState,
}),
{},
),
);
const combinedState = combinedReducers(combinedPreviousState, action).toJS();
const isStateEqualToPreviousState = state =>
Object.values(combinedPreviousState.toJS()).filter(previousStateForComparison =>
Map(fromJS(previousStateForComparison)).equals(Map(fromJS(state))),
).length > 0;
const newState = Object.values(combinedState).reduce(
(accumulatedState, state) =>
isStateEqualToPreviousState(state)
? {
...state,
...accumulatedState,
}
: {
...accumulatedState,
...state,
},
{},
);
return fromJS(newState);
};
};
const mergeReducers = (...reducers) => flatCombineReducers(reducers);
export default mergeReducers;
Esto se llama así:
mergeReducers(reducerA, reducerB)
No produce errores. Básicamente, estoy devolviendo la salida aplanada de la función combineReducers
redux-immutable al redux-immutable .
También he lanzado esto como un paquete npm aquí: redux-immutable-merge-reducers .