どこかの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-dom
のLink
を用いてページ遷移する。
import {Link} from 'react-router-dom'
<Link to="/error">error</Link>
- または、
react-router-dom
のSwitch
と、
import {Route, Switch} from 'react-router-dom'
<Switch>
<Route exact path="/" component={TodoApp}/>
<Route exact path="/error" component={Error}/>
<Switch/>
-
react-router-redux
のpush
を用いてページ遷移する。
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)
-
mapStateToProps
でstate
を受け取り、Componentに渡すものを決める。 -
state.hoge
などはhoge
で扱える。 -
mapDispatchToProps
でdispatch
を受け取り、importしたActionCreaterを用いてComponentに渡すメソッドを決める。 -
connect
でComponentにmapStateToProps
とmapDispatchToProps
を渡す。
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
を設定する。