【问题标题】:ReactJS + Redux: How to structure action creators down to each component?ReactJS + Redux:如何将动作创建者组织到每个组件?
【发布时间】:2017-02-06 21:50:36
【问题描述】:

我有一个名为 App.js 的父组件:

...

render() {
  return (
    <div>
      {React.cloneElement(this.props.children, this.props}
    </div>
  )
}

...

function mapDispatchToProps(dispatch) {
  return (
    actions: bindActionCreators(actions, 
  )
}

export default connect(
  ...,
  mapDispatchToProps
)(App)

道具会被传递给每个组件。我想让每个组件都有它的动作创建者文件,但是我怎样才能将所有动作创建者捆绑在一起,以便动作创建者可以从App.js 级别传递下来?任何其他建议也将不胜感激,让动作创建者深入每个组件。

这是目前的结构:

ComponentOne
..actions.js //action creators
..ComponentOne.js
ComponentTwo
..actions.js //action creators
..ComponentTwo.js
App.js
actions.js//should I compile all the action creators here?

每个actions.js 都会变成这样:

let actions = {
  logSayings() {
    ...
  }
}

export default actions

在此先感谢您,并将投票/接受答案。

REDUX 设置

store.js

import { applyMiddleware, compose, createStore } from 'redux'
import rootReducer from './reducers/rootReducer'
import logger from 'redux-logger'
import thunk from 'redux-thunk'

let finalCreateStore = compose(
  applyMiddleware(thunk, logger())
)(createStore)

export default function configureStore(initialState = {articles: []}) {
  return finalCreateStore(rootReducer, initialState)
}

actions.js

import { hashHistory } from 'react-router'
import { browserHistory } from 'react-router';

let actions = {
  updateBar(status) {
    return {
      type: 'UPDATE_BAR',
      indicator: status
    }
  }
}

export default actions

homeReducer.js

const homeReducer = function(articles = [], action){
  switch(action.type){
    case 'UPDATE_BAR':
      return {
        indicator: action.indicator,
      }

    default:
      return articles
  }
}

export default homeReducer

index.js

import React from 'react';
import {render} from 'react-dom';
import configureStore from '../../redux/store'
import { Provider } from 'react-redux'
import { Router, Route, IndexRoute, hashHistory } from 'react-router'

import App from './components/App'
import Home from './components/Home/Home'

let initialState = {

}

let store = configureStore(initialState)

render(
  <div>
    <Provider store={store}>
      <Router history={hashHistory}>
        <Route
          component={App}
          path='/'
        >
          <IndexRoute component={Home}/>
        </Route>
      </Router>
    </Provider>
  </div>,
  document.querySelector('.wrapper')
)

【问题讨论】:

  • 为什么要在所有子组件上都有动作创建者?为什么你不能将你的动作集中在 App 的 mapDispatchToProps 上?
  • @Robsonsjre 我已经做到了,但它已经到了过于聚集的地步。动作创建者太多,我必须总是滚动浏览不相关的,然后找到我需要的。让特定组件的动作创建者帮助我更快地查找动作创建者并采取行动。

标签: javascript reactjs redux react-jsx


【解决方案1】:

以下是我通常如何构建我的 react/redux 应用程序。

我将操作保留在组件和容器之外。我通常会尝试将我的操作文件命名为特定于我正在构建的应用程序的区域。例如UsersActions、ProductActions、OrdersActions、CheeseActions 也适用...

App
  actions
    CheeseActions.js
    ...
  components
    CheeseBox.js
  containers
    CheeseApp.js
  ...

示例容器 - 让容器处理操作。 (有点像控制器。)

// import the actions that this container needs to do it's job.
import { makeCheese, sellCheese } from '../actions/CheeseActions'
import CheeseBox from '../components/CheeseBox'

class CheeseApp extends Component {

  // Let the container handle the actions
  onPurchasePressed(kind) {
    dispatch(sellCheese(kind))
  }

  // More actions...

  render() {
    return(
      <CheeseBox onPurchase={this.onPurchasePressed.bind(this)} />
    )
  }

}

...

export default connect(mapStateToProps)(CheeseApp)

示例组件 - 我们将让容器处理函数, 使用 this.props.onPurchase('Cheddar')。

// Cheesebox is dumb, keep him that way.
export default class CheeseBox extends Component {

  static propTypes = {
    onPurchase: PropTypes.func.isRequired,
  }

  render() {
    const { onPurchase } = this.props
    return(
      <p>Hi, I love cheese.</p>
      <a onClick={() => onPurchase('Cheddar')} />Buy Now!</a>
    )
  }

}

希望这会有所帮助。如果您有任何问题,请告诉我。

【讨论】:

    【解决方案2】:

    做到这一点的一种方法是采用模块化的方式。这样代码会更干净易读。

    App
      action/
        component1Actions.js
        ...
        componentnActions.js
        actionTypes.js
      components/
        Component1.js
        ...
        Componentn.js
      container/
        Component1.js
        ...
        Componentn.js
      reducers/
        component1Reducer.js
        ...
        componentnReducer.js
    

    上面显示的结构是我遇到的大多数开发人员都使用过的(我更喜欢这种方法)。这是有道理的,因为我们根据文件的性质分隔每个文件。这种方法适用于没有那么多单独文件的中小型项目。

    在大型应用程序中,维护代码通常变得很困难。

    另一个思想流派正在发展domain-wise

    app/
      ...
      App.js
      reducers.js
      routes.js
      product/
        Product.js
        ProductContainer.js
        productReducer.js
        ...
      user/
        User.js
        UserContainer.js
        UserActions.js
        userReducer.js
        ...
    

    这样做的好处是我们可以根据其域来分隔文件。例如一个应用程序需要一个用户组件。如果我们将与该域相关的所有文件保存在一个目录下会更容易。这将使大型应用程序中的应用程序结构更清晰。

    两者都有好处。归根结底,结构并不重要。这更多的是个人选择。

    【讨论】:

    • 非常感谢您的洞察力!尝试根据其域分离文件,并且使用当前设置,我在这样做时遇到了麻烦。我已经用我当前的 redux 设置更新了原始帖子。 reducer 是分开的,但 action 不是。您介意展示如何修改设置以使其基于其域工作吗?提前谢谢!
    • 检查这个 repo github.com/koshuang/react-redux-starter-kit 。这可能会更好地解释这种结构。
    【解决方案3】:

    我可以考虑两种方法:

    • 在 App.js 的 mapDisatchToProps 中将您的 actionObject 合二为一
    • 您的每个组件都可以成为“容器”组件

    示例 1:

    App.js
    
    import actionObj1 from '../actionComponent1'
    
    export default connect(
      mapStateToProps,
      Object.assign({}, actionObj1, actionObj2, actionObj3)
    )(App)
    

    更新(每个子组件成为容器,就像在 App.js 上一样连接):

    Component1.js
    
    export default connect(
      mapStateToProps,
      actionObj1)
    )(Component1)
    

    【讨论】:

    • 您能否详细说明一下如何让每个组件成为一个“容器”组件?我可以将所有内容合并到一个动作创建者表中,而不是导入每个单独的动作创建者表吗?在相同的逻辑中是否有更好的方法?
    • 刚刚签到看看你是否看过我之前的评论。请告诉我。
    【解决方案4】:

    我认为您可能想要查看的是将您的 redux 文件夹结构更改为更加模块化。你不会拥有臃肿的动作文件,而且它应该比包含所有 redux 动作的大型动作/类型/reducer 文件更容易维护。

    我喜欢通过以下方式将我的 redux 操作、类型和 reducer 分成模块:

    在我的src 文件夹中,我将有一个modules 文件夹。在这个modules 文件夹中,我将在我的应用程序状态下为不同类型的数据创建子文件夹。例如,auth 的文件夹、posts 的文件夹和comments 的文件夹。我也会有一个index.js

    在每个文件夹中,我都会有一个actions.js 文件、一个types.js 文件和一个reducer.js 文件。

    在每个action.js 文件中,我只会放置与该类型数据相关的操作。

    例如,在我的posts 文件夹的actions.js 文件中,我将有动作创建者listPostsgetPost 等。我的动作创建者的结构与您可能看到的略有不同,因为我使用的自定义中间件,但您可以用作灵感或复制的中间件的一个很好的例子是:react-redux-universal-hot-exampleThey use a similar style of putting redux items in modules,尽管他们更喜欢将所有动作创建者、类型和缩减程序放在组合文件中,而我喜欢将我的分开。

    import types from './types';
    
    export const getPost = id => ({
      type: types.LOAD_POST_DETAIL,
      responseTypes: [types.LOAD_POST_DETAIL_SUCCESS, types.LOAD_POST_DETAIL_FAILURE],
      promise: client => client.get(`/posts/${id}`),
    });
    
    export const listPosts = () => ({
      type: types.LIST_POSTS,
      responseTypes: [types.LIST_POSTS_SUCCESS, types.LIST_POSTS_FAILURE],
      promise: client => client.get('/posts'),
    });
    

    在每个types.js 文件中,我只会放入与该数据类型相关的类型。 例如,我的types.js 文件可能如下所示:

    export default {
      LOAD_POST_DETAIL: 'LOAD_POST_DETAIL',
      LOAD_POST_DETAIL_SUCCESS: 'LOAD_POST_DETAIL_SUCCESS',
      LOAD_POST_DETAIL_FAILURE: 'LOAD_POST_DETAIL_FAILURE',
    
      LIST_POSTS: 'LIST_POSTS',
      LIST_POSTS_SUCCESS: 'LIST_POSTS_SUCCESS',
      LIST_POSTS_FAILURE: 'LIST_POSTS_FAILURE',
    
      UPDATE_POST: 'UPDATE_POST',
      UPDATE_POST_SUCCESS: 'UPDATE_POST_SUCCESS',
      UPDATE_POST_FAILURE: 'UPDATE_POST_FAILURE',
    };
    

    我的 reducer 将只有特定于该类型数据的 reducer 函数。

    我的posts 文件夹中的reducer.js 文件示例:

    import { combineReducers } from 'redux';
    import type { Reducer } from 'redux';
    import { get, assign } from 'lodash';
    import types from './types';
    
    const all = (
      state = { isFetching: false, data: [] },
      action,
    ) => {
      switch (action.type) {
        case types.LIST_POSTS:
          return assign({}, state, { isFetching: true });
        case types.LIST_POSTS_SUCCESS:
          return assign({}, state, get(action, 'result.data'), { isFetching: false });
        default: return state;
      }
    };
    
    export const reducer = combineReducers({ all });
    

    modules 文件夹的index.js 中,您可以像这样导出所有reducer(在创建redux 存储时合并):

    export { reducer as auth } from './auth/reducer';
    export { reducer as posts } from './posts/reducer';
    export { reducer as comments } from './comments/reducer';
    

    当你import 一个动作时,你会简单地做类似的事情

    import { listPosts } from 'modules/posts/actions';
    

    将你的 redux 文件夹结构设置为更模块化的设计可以让你省去很多麻烦,因为函数的放置位置和导入函数的位置都有一个清晰的模式。

    【讨论】:

    • 感谢您的回复哟。所有动作创建者将被放置在哪里?
    • @JoKo,我更新了我的答案,以便在我的 actions.js 文件中提供一些动作创建者的示例。请注意,我的动作创建者的结构与您通常看到的略有不同,但那是因为我使用了一个基于链接的自定义中间件,但它对我的实现来说是相当具体的,所以在功能上并没有显着不同我链接的那个。我还在 Promise/client 中间件上做了一些注释,并发布了一个示例中间件的链接,您可以从中获得灵感,以及商店项目模块化分离的链接示例。
    【解决方案5】:

    只是想分享一篇我认为可能与此相关的博文https://marmelab.com/blog/2015/12/17/react-directory-structure.html

    但总的来说,我同意 Breet 的回答,不同意博客的观点(:

    【讨论】:

      猜你喜欢
      • 2020-04-10
      • 1970-01-01
      • 2017-10-28
      • 2014-08-26
      • 2017-05-21
      • 1970-01-01
      • 2019-12-28
      • 2020-12-05
      • 2019-08-05
      相关资源
      最近更新 更多