如何避免 redux 中的重复代码(鸭子方法)?

Posted

技术标签:

【中文标题】如何避免 redux 中的重复代码(鸭子方法)?【英文标题】:How to avoid repetitive code in redux (ducks approach)? 【发布时间】:2018-10-29 00:43:59 【问题描述】:

我已经与 ReactRedux 合作了大约 3 年。 我还使用redux-thunk 处理异步内容。

我非常喜欢它们,但最近我注意到我的项目中几乎所有的鸭子都使用相同的动作结构、reducers、选择器等。

例如 - 您有一个应用程序,它有一些用户和交易(或类似)列表、项目详细信息和编辑功能。 所有这些列表或项目都有自己的鸭子(动作、reducers、选择器等)。

下面的代码会更清楚地显示问题:

// ACTIONS

const const setUser = user => (
  type: types.SET_USER,
  payload: user,
);

const cleanUser = () => ( type: types.CLEAN_USER );

const fetchUser = userId => dispatch =>
  dispatch(fetchApi(userRequests.get(userId)))
    .then(response => dispatch(setUser(response)))
    .catch(error => showNotification(error));

// delete, update, etc... user actions

// REDUCER

const userReducer = (state = null, action) => 
  switch (action.type) 
    case types.SET_GROUP_ITEM:
      return action.payload;
    case types.CLEAN_GROUP_ITEM:
      return null;
    default:
      return state;
  
;

上面的代码显示了来自users duckuser 的结构,这对于其他鸭子来说几乎是一样的。

有什么方法可以减少重复代码? 感谢您的提前!

【问题讨论】:

我正准备用我的技巧来回答,但后来我意识到......在你的例子中我实际上没有看到任何重复。当然你有动作,reducers 等等,但那是结构,而不是重复;使用鸭子的重点是让您知道在哪里可以找到东西。我要说的是 redux 的部分想法是明确的,这往往会导致重复。 如果你在谈论样板,我发现当我开始使用 redux-actions 时,我对 Redux 更满意。这提供了一个很好的概述:codeburst.io/redux-actions-through-example-part-1-f5b2dc71de06 @Odalrick,我想说鸭子只是名称不同,但每个实体的动作和减速器几乎相同。 【参考方案1】:

我注意到我项目中几乎所有的鸭子都使用相同的 动作、reducers、选择器等的结构。

我从未在 Redux 中实现 reducks 结构,但我确实发现自己在管理我的域实体(例如 Persons、Orders、Products、等等)。

例如,我似乎总是关心:

    我们当前是否正在获取实体? isFetching 获取实体时是否有任何错误? error 实体的实际数据是什么? data 上次获取实体是什么时候? lastUpdated

此外,域实体一直在添加,因此不断复制和粘贴 reducer/actions 并不理想。我们需要一种在 Redux 中动态存储数据的方法,并且我们希望这些数据始终附加到 isFetchinglastUpdated 等属性。


  "entities": 
    <SOME_ENTITY>: 
      "isFetching" : null    // Am I fetching?
      "lastUpdated": null    // When was I last fetched?
      "data"       : null    // Here's my data!
      "error"      : null    // Error during fetching
    
  

那么,如果我们发出一个带有字符串文字的操作,该字符串文字将用作 Redux 中的键(例如,productsorders)呢?这样,我们可以发出任何可用的有效操作类型(FETCH_REQUEST 等),我们只需更新 entity 键,它将自动为我们在 Store 中开辟空间:

dispatch(
    entity     : "products",
    type       : "FETCH_SUCCESS", 
    data       : [id: 1],
    lastUpdated: Date.now()
);

dispatch(
    entity    : "orders",
    type      : "FETCH_SUCCESS",
    data      : [id: 2, id: 3],
    lastUpdated: Date.now()
);

结果状态


  "entities": 
    "products": 
      "isFetching" : false,
      "lastUpdated": 1526746314736,
      "data"       : [id: 1]
      "error"      : null
    ,
    "orders": 
      "isFetching" : false,
      "lastUpdated": 1526746314943,
      "data"       : [id: 2, id: 3]
      "error"      : null
    
  

通用实体缩减器

function entities (state = , action) 
    switch (action.type) 
        case FETCH_SUCCESS: // fall through
        case FETCH_FAILURE: // fall through
        case FETCH_REQUEST: 
            return Object.assign(, state, 
                [action.entity]: entity(
                    state[action.entity],
                    action
                )
            );
        
        default: 
            return state;
        
    
;

实体缩减器

const INITIAL_ENTITY_STATE = 
    isFetching : false,
    lastUpdated: null,
    data       : null,
    error      : null
;

function entity (state = INITIAL_ENTITY_STATE, action) 
    switch (action.type) 
        case FETCH_REQUEST: 
            return Object.assign(, state, 
                isFetching: true,
                error     : null
            );
        
        case FETCH_SUCCESS: 
            return Object.assign(, state, 
                isFetching : false,
                lastUpdated: action.lastUpdated,
                data       : action.data,
                error      : null
            );
        
        case FETCH_FAILURE: 
            return Object.assign(, state, 
                isFetching : false,
                lastUpdated: action.lastUpdated,
                data       : null,
                error      : action.error
            );
        
    

再次,通过使用通用化简器,我们可以将任何我们想要的内容动态存储到 Redux 中,因为我们使用下面的 entity 字符串作为 Redux 中的键

dispatch(type: "FETCH_REQUEST", entity: "foo");
dispatch(type: "FETCH_REQUEST", entity: "bar");
dispatch(type: "FETCH_REQUEST", entity: "baz");

结果状态


  "entities": 
    "foo": 
      "isFetching": true,
      "error": null,
      "lastUpdated": null,
      "data": null
    ,
    "bar": 
      "isFetching": true,
      "error": null,
      "lastUpdated": null,
      "data": null
    ,
    "baz": 
      "isFetching": false,
      "error": null,
      "lastUpdated": null,
      "data": null
    
  

如果这看起来很有趣,我确实编写了一个小型库(插件!),它完全符合上述描述:

https://github.com/mikechabot/redux-entity https://www.npmjs.com/package/redux-entity

现场演示: http://mikechabot.github.io/react-boilerplate/dist/

也就是说,我并没有推动那个库,我只是想描述一下我遇到的问题所采取的方法。您的操作集可能完全不同,在这种情况下,您仍然可以实现通用模式,但显然减速器的行为会有所不同。

【讨论】:

尽管您在开始时发出警告,但您从未在任何代码示例中使用 redux-thunk。 @Code-Apprentice。酷,然后我成功了,期望这些动作将通过 thunk 发送,但不想混淆讨论另一个库的水域。在此处查看实际的 thunk 用法:github.com/mikechabot/… 我同意这是一种非常干净和干燥的方法。下一步是添加 thunk 操作以发出 HTTP 请求。由于这与问题无关,并且您的答案没有任何代码,因此您可以删除警告。 @Code-Apprentice 我将删除该部分,我同意这可能会造成混淆。 @lux,非常感谢您的回复,代码看起来很棒,我会尝试一下。

以上是关于如何避免 redux 中的重复代码(鸭子方法)?的主要内容,如果未能解决你的问题,请参考以下文章

我们如何避免控制器中方法的重复/重复 - RAILS 3+

如何避免或禁用 Redux 表单文本输入中的自动完成功能?

如何避免重定向两次(react-router-redux)?

有没有办法覆盖 UIPageViewControllerDataSource 中的 viewControllerBefore / After 方法以避免重复代码?

如何修复 Javascript 中的代码以避免 HTML 中的重复元素?

(通用 React + redux + react-router)如何避免在初始浏览器加载时重新获取路由数据?