使用高阶减少器去重复redux动作逻辑

时间:2017-06-07 16:09:59

标签: javascript reactjs redux

我正在重构一个React / Redux应用程序,该应用程序已构建到需要重复删除大量reducer逻辑的程度。我已经研究过高阶减速器了,但是我很难绕过如何让它为我工作。

以下是一个例子:

reducers.js

import components from './entities/components';
import events from './entities/events';
import items from './entities/items';

const entities = combineReducers({
  components,
  items,
  events,
});

我正在使用normalizr。这是我的状态的概念。对于此示例,只需知道事件可以属于组件或项目,但这两者可以区分。

{
  entities: {
    components: {
      component_1: {
        events: ['event_1'],
      },
    },
    items: {
      item_1: {
        events: ['event_2'],
      },
    },
    events: {
      event_1: {
        comment:  "foo"
      },
      event_2: {
        comment:  "bar"
      }
    }
  }
}

我在所有3个reducer中使用EVENT_ADD动作类型。 component.js item.js (下图)的逻辑相同,但两者也有特定于其类型的独立操作。

在这里,我通过使用Immutable.js的.has()函数来检查目标ID是否存在于该部分状态中,从而不必要地将事件添加到错误的位置。

export default (state = getInitState('component'), action) => {
  switch (action.type) {
    case EVENT_ADD:
      return state.has(action.targetId) ?
        state.updateIn(
          [action.targetId, 'events'],
          arr => arr.push(action.event.id)
        ) :
        state;

    [...]
  }
}

event.js 中,我也在这里使用EVENT_ADD将事件的内容添加到该州的那一部分。

export default (state = getInitState('events'), action) => {
  switch (action.type) {
    case EVENT_ADD:
      return state.set(action.event.id, fromJS(action.event));

    [...]
  }
}

我对高阶减速器的理解是我需要做以下一个或两个:

  1. 使用单独的操作类型ADD_EVENT_${entity}(这样我就可以使用 event.js 中的EVENT_ADD_COMPONENTEVENT_ADD_ITEM创建一个事件,无论其父级如何。)
  2. 在我的主reducer中按名称过滤,如bottom of the docs所示,在这种情况下,我不知道如何区分我的initialState和特定于组件和项目的操作,如果我是只为两者共享一个减速器。
  3. 似乎我想要一些 sharedReducer.js ,我可以在其中分享组件和项目之间的逻辑,以执行ADD_EVENTDELETE_EVET等操作。然后还有两个每个实体的独立减速器。我不确定如何才能实现这个目标,或者哪种设计模式最好。

    任何指导都将不胜感激。谢谢!

2 个答案:

答案 0 :(得分:4)

短语"高阶$ THING"通常意味着"一个以$ THING为参数的函数,和/或返回一个新的$ THING" (其中" $ THING"通常是"功能","组件"," reducer"等)。所以,一个"更高阶的减速器"是一个接受一些参数的函数,并返回一个reducer。

根据您链接的Structuring Reducers - Reusing Reducer Logic部分,最常见的高阶减速器示例是一个函数,它采用某种区别值,如ID或动作类型或子字符串,并返回一个使用该函数的新减速器函数区分价值以了解其实际应该响应的时间。这样,您可以创建同一功能的多个实例,并且只能创建" right"即使逻辑的其余部分相同,也会对给定的动作做出响应。

您似乎已经普遍走上了正确的道路,并且您可以通过几种方式组织共享逻辑。

一种方法可能是明确地寻找"普通"首先执行操作并运行该reducer,然后查找特定情况:

export default (state = getInitState('events'), action) => {
    // First check for common cases
    switch(action.type) {
        case EVENT_ADD:
        case OTHER_COMMON_ACTION: {
            state = sharedReducer(state, action);
            break;
        }        
    }

    switch(action.type) {
        case EVENT_ADD:
        case EVENTS_SPECIFIC_ACTION: {
            return eventsSpecificReducer(state, action);
        }
    }

    return state;
}

另一种方法可能是使用reduce-reducers实用程序之类的内容,如Structuring Reducers - Beyond combineReducers部分所示:

const entities = combineReducers({
    components : reduceReducers(componentsReducer, sharedReducer),
    items : reduceReducers(itemsReducer, sharedReducer)
    events,
});

希望这能为您指明正确的方向。如果您有更多问题,请随时在此处或在Discord上的Reactiflux聊天频道中询问 - 邀请链接位于https://www.reactiflux.com。 (注意:我是Redux维护者,"Structuring Reducers" docs section的作者,Reactiflux的管理员。)

答案 1 :(得分:0)

我有一个类似的问题,我的头围绕着更高阶的减速器。即使有编写更高阶函数和组件的经验,还有很多减法器的限制使得它更加困难。

我提出了这样的解决方案:

export default function withSomething(reducer) {
    const initialState = {
        something: null,
        ...reducer(undefined, {}),
    };

    return function (state = initialState, action) {
        let newState = state;

        switch (action.type) {
            case actionTypes.SET_SOMETHING:
                newState = {
                    ...state,
                    something: action.something,
                };
                break;
            default:
        }

        return reducer(newState, action);
    };
}

主要"技巧"正在调用具有未定义状态的包装器reducer以获取其初始状态并向其添加属性,并将由HOR的动作处理产生的新状态传递给包装的reducer。你可以通过不调用包装的reducer进行优化,如果newState!=在switch结构之后的状态,但我觉得这种方式更灵活,因为它可能让包装的reducer处理和已经处理过的动作并用它自己的部分做一些事情国家。