在不添加嵌套的情况下组合redux reducer

Mic*_*per 24 javascript redux

我有一个场景,我有2个减速器是a的结果combineReducers.我想将它们组合在一起,但在嵌套时将它们的键保持在同一级别.

例如,给出以下减速器

const reducerA = combineReducers({ reducerA1, reducerA2 })
const reducerB = combineReducers{{ reducerB1, reducerB2 })
Run Code Online (Sandbox Code Playgroud)

我想最终得到一个像这样的结构:

{
    reducerA1: ...,
    reducerA2: ...,
    reducerB1: ...,
    reducerB2: ...
}
Run Code Online (Sandbox Code Playgroud)

如果我combineReducers再次使用reducerA并且reducerB喜欢这样:

const reducer = combineReducers({ reducerA, reducersB })
Run Code Online (Sandbox Code Playgroud)

我最终得到了一个结构:

{
    reducerA: {
        reducerA1: ...,
        reducerA2: ...
    },
    reducerB: {
        reducerB1: ...,
        reducerB2: ...
    }
}
Run Code Online (Sandbox Code Playgroud)

我不能结合reducerA1,reducerA2,reducerB1reducerB2在一个单一的combineReducers呼叫为reducerAreducerB被提供给我已经从不同的NPM包相结合.

我已经尝试使用reduce-reducers库将它们组合在一起并将状态缩小在一起,这是我从查看redux文档得到的一个想法,如下所示:

const reducer = reduceReducers(reducerA, reducerB)
Run Code Online (Sandbox Code Playgroud)

不幸的是,这不起作用,生成器产生的reducer combineReducers如果找到未知的键就会发出警告,并在返回状态时忽略它们,因此生成的结构只包含以下内容reducerB:

{
    reducerB1: ...,
    reducerB2: ...
}
Run Code Online (Sandbox Code Playgroud)

我真的不想实现我自己combineReducers没有强制执行结构,如果我不需要,所以我希望有人知道另一种方式,内置到redux或可以帮助的库我这个.有任何想法吗?


编辑:

提供了一个答案(现在似乎已被删除)建议使用flat-combine-reducers库:

const reducer = flatCombineReducers(reducerA, reducerB)
Run Code Online (Sandbox Code Playgroud)

这一步不是减少减速器接近,因为它设法保持同时从保持状态reducerAreducerB,但警告消息仍在生产,这使我怀疑我所观察到的消失状态之前并没有combineReducers把它扔了,但相反,还有其他一些与reduce-reducers实现相关的东西.

警告信息是:

意外的键"reducerB1","reducerB2"在减速器收到的先前状态中找到.预计会找到一个已知的减速器键:"reducerA1","reducerA2".意外的键将被忽略.

意外的键"reducerA1","reducerA2"在减速器收到的先前状态中找到.预计会找到一个已知的减速器键:"reducerB1","reducerB2".意外的键将被忽略.

如果我进行生产构建,则警告消失(这是许多反应/减少警告的方式),但我宁愿它们根本不出现.

我还做了一些其他库的搜索,发现了redux-concatenate-reducers:

const reducer = concatenateReducers([reducerA, reducerB])
Run Code Online (Sandbox Code Playgroud)

这与flat-combine-reducers具有相同的结果,因此继续搜索.


编辑2:

现在有几个人已经提出了一些建议但到目前为止还没有任何建议,所以这里有一个测试来帮助:

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)
    })
})
Run Code Online (Sandbox Code Playgroud)

目标是让测试通过并且不在控制台中产生任何警告.


编辑3:

添加了更多测试以涵盖更多案例,包括在初始创建后处理操作以及是否使用初始状态创建存储.

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)
    })
})
Run Code Online (Sandbox Code Playgroud)

上述mergeReducers实现通过了所有测试,但仍然向控制台发出生产者警告.

  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
Run Code Online (Sandbox Code Playgroud)

重要的是要注意,正在打印的警告是紧接着的测试用例,并且combineReducers减速器只会打印一次唯一的警告,所以因为我在测试之间重复使用减速器,所以仅在第一个测试用例中显示警告产生它(我可以在每次测试中结合使用减速器来防止这种情况,但作为我正在寻找它的标准根本不能产生它们,我现在很高兴这一点).

如果你正在尝试这个,我不介意是否mergeReducers接受2个reducer(如上所述),reducers数组或reducers对象(如combineReducers).其实,我不介意如何,因为它并不需要创造任何变化是只要实现reducerA,reducerB,reducerA1,reducerA1,reducerB1reducerB2.


编辑4:

我目前的解决方案是根据Jason Geomaat的回答进行修改的.

我们的想法是使用以下包装器使用先前调用的键过滤提供给reducer的状态:

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;
    };
}
Run Code Online (Sandbox Code Playgroud)

我使用redux-concatenate-reducers库合并了滤波后的Reducer的结果(可能使用了flat-combine-reducers,但前者的合并实现看起来更健壮了).该mergeReducers功能如下:

const mergeReducers = (...reducers) => concatenateReducers(reducers.map((reducer) => filterReducer(reducer))
Run Code Online (Sandbox Code Playgroud)

这被称为如下:

const store = createStore(mergeReducers(reducerA, reducerB)
Run Code Online (Sandbox Code Playgroud)

这会通过所有测试,并且不会产生任何由使用的减速器发出的警告combineReducers.

我唯一不确定的是knownKeys通过使用INIT动作调用reducer来播种数组的位置.它有效,但感觉有点脏.如果我不这样做,产生的唯一警告是如果创建存储的初始状态(在解析reducer的初始状态时不会过滤掉额外的键.

Jas*_*aat 4

好吧,决定这样做是为了好玩,而不是太多代码......这将包装一个减速器并只为其提供它自己返回的键。

// 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;
  };
}
Run Code Online (Sandbox Code Playgroud)

在您的测试中:

const reducerA = filterReducer(combineReducers({ reducerA1, reducerA2 }))
const reducerB = filterReducer(combineReducers({ reducerB1, reducerB2 }))
Run Code Online (Sandbox Code Playgroud)

注意: 这确实打破了减速器在给定相同输入的情况下始终提供相同输出的想法。创建减速器时接受键列表可能会更好:

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'])
Run Code Online (Sandbox Code Playgroud)