Skip to content

Instantly share code, notes, and snippets.

@nukotsuka
Created March 26, 2018 06:36
Show Gist options
  • Save nukotsuka/8719a7f26b673f8f4d63ecf568bcc9e3 to your computer and use it in GitHub Desktop.
Save nukotsuka/8719a7f26b673f8f4d63ecf568bcc9e3 to your computer and use it in GitHub Desktop.
Ruduxの書き方手順
@nukotsuka
Copy link
Author

nukotsuka commented Mar 26, 2018

インストール

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

@nukotsuka
Copy link
Author

nukotsuka commented Mar 26, 2018

書く手順

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を用いる場合は以下。
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">エラー</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を設定する。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment