ReactJS + Redux: как структурировать создателей действий до каждого компонента?

У меня есть один родительский компонент с именем 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')
)

Ответ 1

Вот как я обычно структурирую приложение action/redux.

Я сохраняю действия вне компонентов и контейнеров. Обычно я пытаюсь назвать свои файлы действий конкретными для областей приложения, которое я создаю. например UsersActions, ProductActions, OrdersActions, CheeseActions тоже работают...

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

Пример контейнера. Позвольте контейнеру обрабатывать действия. (Kinda как контроллер.)

// import the actions that this container needs to do it 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

Структура, показанная выше, - это то, что использовали большинство разработчиков, с которыми я столкнулся (я предпочитаю этот подход). Это имеет смысл, потому что мы разделяем каждый файл на основе характера этого файла. Этот подход подходит для малых и средних проектов, где не так много отдельных файлов.

В больших приложениях часто бывает сложно поддерживать код.

Еще одна школа мысли идет доменная

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

Преимущество этого в том, что мы отделяем файлы на основе своего домена. Напр. для приложения требуется компонент User. Было бы проще, если бы мы сохранили все файлы, связанные с этим доменом, в одном каталоге. Это упростит работу приложения в больших приложениях.

Оба имеют преимущества. В конце концов, структура не имеет значения. Это скорее личный выбор.

Ответ 3

Я могу думать о двух подходах:

  • Объединяя ваши объекты actionObjects в одном на App.js mapDisatchToProps
  • Каждый из ваших компонентов может стать компонентом "container"

Пример 1:

App.js

import actionObj1 from '../actionComponent1'

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

UPDATE (каждый дочерний компонент становится контейнером, просто подключитесь как к App.js):

Component1.js

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

Ответ 4

Я думаю, что что-то, что вы, возможно, захотите посмотреть, это изменить структуру каталогов редуксов на более модульную. У вас не будет раздутых файлов действий, и их должно быть проще поддерживать, чем иметь большие файлы действий/типа/редуктора, которые охватывают все ваши действия по сокращению.

Мне нравится отделять свои действия, типы и редукторы свода в модулях следующим образом:

В моей папке src у меня будет папка modules. В этой папке modules у меня будут подпапки для разных типов данных в моем состоянии приложения. Например, папка для auth, папка для posts и папка для comments. У меня также будет index.js.

В каждой папке у меня будет файл actions.js, файл types.js и файл reducer.js.

В каждом файле action.js я буду размещать только в действиях, относящихся к этому типу данных.

Например, в файле actions.js моей папки posts у меня будут создатели действий listPosts, getPost и т.д. Структура моего создателя действия немного отличается от того, что вы можете видеть, потому что пользовательского промежуточного программного обеспечения, которое я использую, но отличный пример промежуточного программного обеспечения, который вы можете использовать как вдохновение или копию, находится здесь: react-redux-universal-hot-example. Они используют аналогичный стиль размещения элементов redux в модулях, хотя они предпочитают помещать их всех создателей действий, типов и редукторов в комбинированные файлы, в то время как Мне нравится держать меня отдельно.

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.js в моей папке posts:

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

В index.js папки modules вы можете экспортировать все свои редукторы (которые будут объединены при создании хранилища редуктов) следующим образом:

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

Настройка структуры папок сокращений, чтобы иметь более модульный дизайн, может сэкономить вам много головной боли, так как есть четкая схема, где будут размещаться функции и откуда импортировать свои функции.

Ответ 5

Просто хотел поделиться сообщением в блоге, в котором я мог бы быть здесь, https://marmelab.com/blog/2015/12/17/react-directory-structure.html

Но в целом я согласен с ответом Бритта и не согласен с мнением блога (: