最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - ReactJS + Redux: How to structure action creators down to each component? - Stack Overflow

programmeradmin1浏览0评论

I have one parent component called 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)

And the props would be passed down to each component. I would like to have each component to have its action creators file, but then how can I tie all the action creators into one so that the action creators can be passed down from the App.js level? Any other suggestions would be appreciated too to have action creators down to each component.

Here is the structure so far:

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?

And each actions.js would be made like so:

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

export default actions

Thank you in advance and will upvote/accept answer.

REDUX SET UP

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')
)

I have one parent component called 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)

And the props would be passed down to each component. I would like to have each component to have its action creators file, but then how can I tie all the action creators into one so that the action creators can be passed down from the App.js level? Any other suggestions would be appreciated too to have action creators down to each component.

Here is the structure so far:

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?

And each actions.js would be made like so:

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

export default actions

Thank you in advance and will upvote/accept answer.

REDUX SET UP

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')
)
Share Improve this question edited Feb 28, 2017 at 18:18 Jo Ko asked Feb 6, 2017 at 21:50 Jo KoJo Ko 7,57516 gold badges68 silver badges127 bronze badges 2
  • Why do you want to have action creators on all your child components? why you cant centralize your actions on App's mapDispatchToProps? – Robsonsjre Commented Feb 6, 2017 at 22:37
  • @Robsonsjre I've done that but it got to a point where it got too clustered. Too many action creators, and I would have to always scroll through the irrelevant ones and find the one that I need. Having action creators for a particular component helps me to look up and take action much more quicker on action creators. – Jo Ko Commented Feb 6, 2017 at 22:39
Add a comment  | 

5 Answers 5

Reset to default 6 +25

Here's how I usually structure my react/redux app.

I keep actions outside of the components & containers. I usually try and name my action files specific to areas of the app that I'm building. e.g. UsersActions, ProductActions, OrdersActions, CheeseActions works too...

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

Example Container - Let the container handle the actions. (Kinda like a controller.)

// 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)

Example Component - We're going to let the container handle the function, using 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>
    )
  }

}

Hopefully this is helpful. Let me know if you have any questions.

One way to do this is going modular nature wise. This way the code will be cleaner and readable.

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

The structure shown above is what most of the developers I have encountered have used (I prefer this approach). This makes sense because we separate each file based on the nature of that file. This approach is suitable for small to medium projects where there are not that many separate files.

In large apps, it often becomes difficult to maintain the code.

Another school of thought is going 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
    ...

The benefit of this is that we separate files based on its domain. For e.g. an app needs a User component. It would be easier if we kept all the files related to that domain under one directory. This would make the app structure cleaner in large applications.

Both have benefits. At the end of the day, the structure doesn't matter. It is more of a personal choice.

I can think about two approaches:

  • Combining your actionObjects all in one at App.js's mapDisatchToProps
  • Each of your components can become a 'container' component

Example 1:

App.js

import actionObj1 from '../actionComponent1'

export default connect(
  mapStateToProps,
  Object.assign({}, actionObj1, actionObj2, actionObj3)
)(App)

UPDATE (each child component become container, just connect like on App.js):

Component1.js

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

I think that something that you may want to look at is changing your redux folder structure to be more modular. You wont have bloated action files and it should be easier to maintain than have large action/type/reducer files that encompass all of your redux actions.

I like to separate my redux actions, types, and reducers into modules in the following way:

In my src folder, I will have a modules folder. In this modules folder, I will have sub-folders for different types of data in my application state. For example, a folder for auth, a folder for posts, and a folder for comments. I will also have a index.js.

In each folder, I will have an actions.js file, a types.js file, and a reducer.js file.

In each action.js file, I will only place in actions that pertain to that type of data.

For example, in the actions.js file of my posts folder, I will have the action creators listPosts, getPost, etc. The structure of my action creator is slightly different from what you might see because of the custom middleware that I use, but a great example of a middleware you can use as inspiration or copy is here: react-redux-universal-hot-example. They use a similar style of putting redux items in modules, although they prefer to put them all action creators, types, and reducers in combined files while I like to keep mine separate.

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'),
});

In each types.js file, I will only place in types that pertain to that type of data. For example, my types.js file might look like this:

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',
};

My reducer will only have reducer functions specific to that type of data.

An example for the reducer.js file in my posts folder:

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 });

In the index.js of the modules folder, you can export all of your reducers (to be combined when you create your redux store) like so:

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

When you import an action, you would simply do something like

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

Setting up your redux folder structure to have a more modular design can save you a lot of headache since there is a clear pattern to where functions will be placed and where to import your functions from.

Just wanted to share a blog post I tought might be relevant here https://marmelab.com/blog/2015/12/17/react-directory-structure.html

But in general I agree with Breet's answer, and disagree with the blog's opinion (:

发布评论

评论列表(0)

  1. 暂无评论