使用 redux combineReducers 减少整个子树

Reducing an entire subtree with redux combineReducers

我有一个看起来像这样的 reducer 树:

module.exports = combineReducers({
    routing: routeReducer,
    app: combineReducers({
        setup: combineReducers({
            sets,
            boosters
        }),
        servers: combineReducers({
            servers
        })
    })
});

现在 setup 键包含一个表单,一旦我们提交它就需要重新设置。但是我无法访问整个 setup 树,因为使用 combineReducers 意味着 reducer 仅在树的叶节点处操作数据(在本例中为 setsboosters)。

我的第一个冲动是制作一个像这样减少整个设置树的函数:

function setup(state, action){
    //If there's an action that affects this whole tree, handle it
    switch(action.type){
        case "FORM_SUBMIT": //DO STUFF
        break;
    }

    //Otherwise just let the reducers care about their own data
    return combineReducers({
                sets,
                boosters
    })(state);
}

但这不起作用,而且还弄乱了我第一个代码示例的漂亮树结构。

redux 有更好的解决方案吗?

combineReducers 是一个很好的模式,因为它倾向于强制执行这样的想法,即 reducer 应该限定在 store 的非重叠子集中,与 store 本身的结构分离。它认为你应该减少树叶,而不是树枝,它处理树枝的减少。

也就是说,使用替代模式可能有充分的理由。正如我在稍微 中提到的,您可以选择不使用 combineReducers 并根据需要分解您的减速器。

在你的情况下,你可以装饰你的内心 combineReducers:

module.exports = combineReducers({
    routing: routeReducer,
    app: combineReducers({
        setup: setupReducer(
            combineReducers({
                sets,
                boosters
            })
        ),
        servers: combineReducers({
            servers
        })
    })
});

这里,setupReducer是一个higher-order function。这可能很难推理,但我是这样处理的:

  • 我们知道 setupReducer 将 reducer 作为参数,因为我们将 combineReducers 的结果传递给它。
  • 我们知道由combineReducers编辑的reducerreturn的签名是(state, action) => state
  • 我们还知道 setupReducer 必须 return 一个减速器,它又是签名的函数 (state, action) => state.

换句话说,它需要一个减速器,return是一个减速器:((state, action) => state) => ((state, action) => state)。所以它可能看起来像:

function setupReducer(subReducer) {
    return (state, action) => {
        //If there's an action that affects this whole tree, handle it
        switch(action.type){
            case "FORM_SUBMIT": 
                // ... create newState
                return newState;
            default:
                return subReducer(state, action);
        }
    }
}

我保留了上面的逻辑流程,但请注意,您可能希望无条件地调用 subReducer 然后修改其输出。否则,您必须确保未调用的分支始终生成相同形状的对象,这似乎是耦合的潜在粘点。

@acjay 的回答是个好主意!我只是想使用旧方法 reducer(state, action) 而不是高阶函数。所以我创建了一个以主从关系组合reducer的方法。

主从

export default function masterSlaveReducer(reducerMaster, reducerSlave) {
    return (state, action) => {
        const newState = reducerMaster(state, action);
        if(newState === state){
            return reducerSlave(state, action);
        }

        return newState;
    };
}

Miguel 的代码将以这种方式使用:

module.exports = combineReducers({
    routing: routeReducer,
    app: combineReducers({
        setup: masterSlaveReducer(
            setup, // setup() from Miguel's question
            combineReducers({
                sets,
                boosters
            })
        ),
        servers: combineReducers({
            servers
        })
    })
});