【问题标题】:Redux handling domain specific actions in a global componentRedux 在全局组件中处理特定于域的操作
【发布时间】:2016-09-30 15:39:58
【问题描述】:

为了模块化,我的 Redux 应用程序被分解为单独的组件,app 域是一种共享基础架构组件,用于处理常见的 ui 更改,例如加载指示器。

app/ <- Global app  
---app_reducer.js  
---constants.js  
---actions.js
---...(react components)
users/
---users_reducer.js
---constansts.js
---actions.js
---...(react components)
products/
---products_reducer.js
---constants.js
---actions.js
---...(react components)

在我的根减速器中,我使用combineReducers 函数根据我的域很好地拆分状态树:

import { combineReducers } from 'redux';
import App from './app/app_reducer'
import Users from './users/users_reducers'
import Products from './products/products_reducers'

export default combineReducers({
  App,
  Users,
  Products
})

所以我的状态树看起来像这样:

{
   app: 
     isLoading: false,
   users:
     ['user1', 'user2', 'user3']
   products:
     ['product1', 'product2'],
     lastFetch: 1246578942621
}

好的,现在我想向usersproducts 添加一些异步功能,它们从服务器获取数据。使用redux-thunk 或promise 中间件之类的东西是微不足道的。 所以users/actions.js 的动作创建者可能看起来像这样:

import { LOAD_USERS_REQUEST, LOAD_USERS_SUCCESS } from './actions'

// Using redux-thunk
export function LoadUsers(){
  return function(dispatch){
     dispatch({type: LOAD_USERS_REQUEST})
     fetch('myserver.com/users')
     .then(function(data){
        dispatch({type: LOAD_USERS_SUCCESS, data })
     })
  }
}

请记住,products 也可能会执行类似 fetch 自身的操作。
现在,usersReducer 处理这些操作并在users 子树上设置不同的状态道具。问题是,我还想在app 子树中设置一个loading: true,这样我的高级app 组件就会呈现一个很好的加载指示器。但是usersReducer 没有得到状态树的那个slice

如果不编写自定义combineReducers 将向所有reducer 发送整个状态树,那么实现这一点的优雅方法是什么?我正在寻找一个干净且可扩展的解决方案。 以下是我考虑的几种方法:

1) 让appReducer 响应每个新请求。 这意味着每次为fetch创建新的action类型时,都需要在app/app_reducer.js中添加:

import * as productTypes from '../products/constants'
import * as userTypes from '../users/constants'

export default function(state = {}, action){
  switch(action.type){
    case productTypes.LOAD_USERS_REQUEST:
    case productTypes.LOAD_PRODUCTS_REQUEST:
      return({...state, isLoading: true});
    case productTypes.LOAD_USERS_SUCCESS:
    case productTypes.LOAD_PRODUCTS_SUCCESS:
      return({...state, isLoading: false})
    default:
      return(state);
  }
}

问题在于,我必须修改appReducer,为我创建的每个需要加载指示器的新操作。

2) 第二种方法是创建另外两个名为 startLoadingstopLoading 的操作,它们将从异步操作中调用,如下所示:

import { LOAD_USERS_REQUEST, LOAD_USERS_SUCCESS } from './actions'
import { START_LOADING, STOP_LOADING } from '../app/actions'    

export function LoadUsers(){
  return function(dispatch){
     dispatch({type: START_LOADING})
     dispatch({type: LOAD_USERS_REQUEST})
     fetch('myserver.com/users')
     .then(function(data){
        dispatch({type: STOP_LOADING})
        dispatch({type: LOAD_USERS_SUCCESS, data })
     })
  }
}

乍一看这似乎是个好主意,但调用 2 个动作而不是一个动作看起来很浪费。

我想这是一个常见问题,但我找不到任何实用和“现实生活”的建议。 我很高兴听到任何其他解决此问题的方法或对给定建议的想法。

【问题讨论】:

  • 如果是我,我会采取第二种方法。对于你的动作创建者来说,这是一个额外的样板,但我觉得它比替代方案更干净——否则你的顶级减速器会变得很长。
  • 是的,但是你不同意一个接一个地调用两个动作(这会导致另一个状态更新)看起来很浪费吗?它可能不是那么坏,但我担心这种范式可能会导致更多的顺序动作被调用。即添加一个 START_PROFILING/STOP_PROFILING 操作,在 fetch 运行时向 UI 呈现信息
  • 好吧,无论哪种方式,你都会导致两个状态更新——一个在你的 users reducer 中,一个在 app reducer 中——所以我怀疑它会产生很大的不同。我认为增加的清晰度使它值得 - 不过,这完全是一个品味问题!
  • @JoeClay 据我了解,所有减速器都作为 RootReducer 组合在一起。每个动作调度调用都会调用一次这个reducer,然后它会调用它的所有子reducer(输出新的状态树),然后重新渲染所有订阅了store并传递新状态树的容器元素。所以如果我一个接一个地调用两个调度,这个循环(rootReducer -> re-render)将发生两次。当然也有 react 的优化,比如 shadow dom 的使用,但还是比第一种更浪费

标签: javascript reactjs redux redux-thunk


【解决方案1】:

Redux FAQ 涵盖了此主题。目前,是的,答案是“编写额外的自定义 reducer 逻辑”,或者“使用 getState 将更多数据放入操作中”。

也就是说, 目前正在讨论一个 PR,它将让 combineReducers 将整个状态作为附加参数传递下去。见Feature Request: Allow reducers to consult global state

另外,回答 Pavel Tarno 的评论:是的,每个调度都会调用每个子减速器函数,如果您正在使用combineReducers(请参阅Redux FAQ);是的,每个分派都会调用所有订阅者;但是 React Redux connect() 函数只会强制重新渲染订阅的组件,这些组件从 mapStateToProps 返回修改/更新的结果。此外,由于 React 的批处理,即使在一个事件循环中多次连续分派每次都导致给定组件需要重新渲染,也可能只有一个 实际 重新渲染。 (最后,它是“虚拟 DOM”,而不是“影子 DOM”。)

【讨论】:

  • 我有点惭愧我错过了这个非常方便的常见问题解答
猜你喜欢
  • 1970-01-01
  • 2011-05-19
  • 2020-05-08
  • 2023-04-04
  • 2021-05-16
  • 2023-03-09
  • 2017-02-20
  • 2010-10-31
  • 1970-01-01
相关资源
最近更新 更多