LoginSignup
25
19

More than 5 years have passed since last update.

Reduxの書き方手順

Last updated at Posted at 2018-06-26

どこかのReduxのチュートリアルをやった時に書いたメモをまとめました。
この手順通りに書いていくのが自分にとって一番わかりやすかったです。

インストール

Redux

npm install --save redux react-redux

Router

npm install --save react-router-dom react-router-redux@next history

Midleware

npm install --save redux-logger redux-thunk

PropTypes

npm install --save prop-types

書く手順

Reducer

src/reducers/tasks.js

const initialState = {
  task: '',
  tasks: []
};

export default function tasksReducer(state = initialState, action) {
  switch (action.type) {
    case 'INPUT_TASK':
      return {
        ...state,
        task: action.payload.task
      };
    case 'ADD_TASK':
      return {
        ...state,
        tasks: state.tasks.concat([action.payload.task])
      };
    default:
      return state;
  }
}
  • initialStateでStoreに保存するStateを決める。
  • Reducerが多く、分割する場合はcombineReducerを用いる。(createStore.jsでまとまる場合は不要(?))

import { combineReducers } from 'redux';

const rootReducer = combineReducers({
  messageModal,
  aService,
  errorMessage,
  router
});

export default rootReducer;

Store

src/createStore.js

import {
  createStore as reduxCreateStore,
  combineReducers,
  applyMiddleware
} from 'redux'
import {routerReducer, routerMiddleware} from 'react-router-redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import tasksReducer from '../reducers/tasks'

export default function createStore(history) {
  return reduxCreateStore(
    combineReducers({
      tasks: tasksReducer,
      router: routerReducer,
    }),
    applyMiddleware(
      routerMiddleware(history),
      logger,
      thunk
    )
  );
}
  • ReducerとMiddlewarをここでStoreに紐づける。
  • combineReducerでは分割された子Reducer名と同じキーのstateが使用できる。(ex. state.tasks, state.router)
  • Redux DevToolsを用いる場合は以下。
src/createStore.js
import {
  createStore as reduxCreateStore,
  combineReducers,
  applyMiddleware,
  compose
} from 'redux'
import {routerReducer, routerMiddleware} from 'react-router-redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import userReducer from "./reducers/userReducer";
import countReducer from "./reducers/countReducer";

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

export default function createStore(history) {
  return reduxCreateStore(
    combineReducers({
      user: userReducer,
      count: countReducer,
      router: routerReducer,
    }),
    composeEnhancers(
      applyMiddleware(
        routerMiddleware(history),
        logger,
        thunk
      )
    )
  );
}

Root

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import {Route} from 'react-router-dom'
import {Provider} from 'react-redux'
import {ConnectedRouter} from 'react-router-redux'
import createBrowserHistory from 'history/createBrowserHistory'
import createStore from './store/index'
import TodoApp from './containers/TodoApp'
import Error from './components/Error'

const history = createBrowserHistory();
const store = createStore(history);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedRouter history={history}>
      <div>
        <Route exact path="/" component={TodoApp}/>
        <Route exact path="/error" component={Error}/>
      </div>
    </ConnectedRouter>
  </Provider>,
  document.getElementById('root')
);
  • ProviderでComponentにstoreを紐づける。
  • ConnectedRouterでComponentにhistoryを紐づける。
  • RouteでRoutingを行う。
  • ここにはないが、react-router-domLinkを用いてページ遷移する。
import {Link} from 'react-router-dom'
<Link to="/error">error</Link>
  • または、react-router-domSwitchと、
import {Route, Switch} from 'react-router-dom'

<Switch>
  <Route exact path="/" component={TodoApp}/>
  <Route exact path="/error" component={Error}/>
<Switch/>
  • react-router-reduxpushを用いてページ遷移する。
import {push} from 'react-router-redux'

dispatch(push('/error'))

ActionCreater

src/actions/taks.js
export const inputTask = (task) => ({
  type: 'INPUT_TASK',
  payload: {
    task
  }
});

export const addTask = (task) => ({
  type: 'ADD_TASK',
  payload: {
    task
  }
});

flux-standard-action規約

{
  type: FOO_TYPE,      // must
  payload: {object},   // optional
  meta: {object},      // optional
  error: false, true, undefined, null, ... // optional
}

thunkを用いた非同期なActionCreater

export const signIn = (name, email, password) => ({
  type: 'SIGN_IN',
  payload: {name, email, password}
});

export const asyncSignIn = (email, password) => {
  return dispatch => {
    request
      .get(url)
      .set('Content-Type', 'application/json')
      .set('Access-Control-Allow-Origin', '*')
      .query({email: email, password: password})
      .end(function (err, res) {
        if (err) {
          console.log(err);
        } else {
          console.log(res);
          dispatch(signIn(res.body.name, res.body.email, res.body.password));
          dispatch(push('/count'));
        }
      });
  };
};

thunkを用いた複数のActionCreaterをまとめたActionCreater


export const inputEmail = (email) => ({
  type: 'INPUT_EMAIL',
  payload: {email}
});

export const inputPassword = (password) => ({
  type: 'INPUT_PASSWORD',
  payload: {password}
});

export const inputAction = (email, password) => {
  return (dispatch) => {
    dispatch(inputEmail(email));
    dispatch(inputPassword(password));
  };
};

Container

src/containers/Ranking.js
import {connect} from 'react-redux'
import {push} from 'react-router-redux'
import TodoApp from '../components/TodoApp'
import {inputTask, addTask} from "../actions/tasks";

const mapStateToProps = ({tasks}) => {
  return {
    task: tasks.task,
    tasks: tasks.tasks
  };
};

const mapDispatchToProps = (dispatch) => ({
  addTask: task => dispatch(addTask(task)),
  inputTask: task => dispatch(inputTask(task)),
  redirectToError: () => dispatch(push('/error'))
});

export default connect(mapStateToProps, mapDispatchToProps)(TodoApp)
  • mapStateToPropsstateを受け取り、Componentに渡すものを決める。
  • state.hogeなどはhogeで扱える。
  • mapDispatchToPropsdispatchを受け取り、importしたActionCreaterを用いてComponentに渡すメソッドを決める。
  • connectでComponentにmapStateToPropsmapDispatchToPropsを渡す。

Component

src/component/TodoApp.js
import React from 'react'
import PropTypes from 'prop-types'

const TodoApp = ({task, tasks, inputTask, addTask, redirectToError}) => (
  <div>
    <input type="text" onChange={(e) => inputTask(e.target.value)}/>
    <input type="button" value="add" onClick={() => addTask(task)}/>
    <ul>
      {
        tasks.map((item, i) => {
          return (
            <li key={i}>{item}</li>
          );
        })
      }
    </ul>
    <button onClick={() => redirectToError()}>エラーページへ</button>
  </div>
)

TodoApp.propTypes = {
  task: PropTypes.string.isRequired,
  tasks: PropTypes.arrayOf(PropTypes.string.isRequired).isRequired,
  inputTask: PropTypes.func.isRequired,
  addTask:  PropTypes.func.isRequired,
  redirectToError: PropTypes.func.isRequired
}

export default TodoApp
  • Containerから渡されたmapStateToProps, mapDispatchToPropsの中身を引数に取る。
  • PropTypesを用いてpropTypesを設定する。
25
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
25
19