対象者:Reactの基本を理解している人 / Reduxについて勉強している人
前:ReactJS入門@ES6:ReactRouter編
次:未定
まえがき
Reduxの目的はStateを一元管理することです。
素のReactでは、Stateはコンポーネントが管理します。
コンポーネントの数が多くなり、親から子、子から子へとStateを継承したり、
コンポーネント間の連携などを考えだすと、Stateをどこかで一元管理し、
コンポーネントはイベントを受け、Stateを変更する命令を出すことに専念したくなります。
その仕組と制約を実現するのがReduxの役目だと、私は考えています。
まずはReduxを使わないカウンターをつくってみる
カウンターをつくりましょう、カウンター。
画面にカウント回数と、プラスボタンとマイナスボタンが表示されているだけのやつです。
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
}
}
onIncrement() {
this.setState({ count: this.state.value + 1 });
}
onDecrement() {
this.setState({ count: this.state.value - 1 });
}
render() {
return (
<div>
カウント: {this.state.count} 回
<br />
<button onClick={::this.onIncrement}>プラス</button>
<button onClick={::this.onDecrement}>マイナス</button>
</div>
)
}
}
できました。
簡単ですね。
Reduxの前準備
さて、このカウンターをRedux化していきます。
まずRedux化に必須となるActions,Reducer,Storeを作成します。
1. Actions
まずはActionsを考えます。
Actionsとは、命令コマンドのことだと思って下さい。
さて、このカウンターではどんな命令コマンドが必要でしょうか。
- カウントをアップせよ
- カウントをダウンせよ
まあこの2つ程度ですね。
その命令コマンドをコードに書き起こします。
const INCREMENT_COUNTER = {
type: 'INCREMENT_COUNTER'
}
const DECREMENT_COUNTER = {
type: 'DECREMENT_COUNTER'
}
終わりです。
簡単ですね。
2. Reducer
次に、Reducerを考えます。
Reducerは、StateとActionsを受け取り、変更したStateを返すだけの処理です。
今回の場合、INCREMENT_COUNTERというActionsを受け取ったらStateのcountをプラスし、
DECREMENTの場合その逆を行うだけの処理を行います。
まあ見れば分かります。
function counterReducer(state = {count: 0}, action) {
switch (action.type) {
case 'INCREMENT_COUNTER':
return {count: state.count + 1}
case 'DECREMENT_COUNTER':
return {count: state.count - 1}
default:
return state
}
}
簡単ですね。
3. Store
さて、最後にStoreを作成しましょう。
Storeは、Stateを管理するものです。
StateはReducerが変更します。
よって、StoreはReducerから作成します。
const store = Redux.createStore(counterReducer);
終わりです。
簡単ですね。
コンポーネント変更の前準備
次にコンポーネントを変更する前準備を行います。
コンポーネントが持つStateはStoreに移動し、
ボタン押下時の処理はReducerに移動しました。
よって、コンポーネントに対し、StateとActionsを割り当てる必要があります。
function mapStateToProps(state) {
return {
count: state.count
};
}
function mapDispatchToProps(dispatch) {
return {
onIncrement: () => dispatch(INCREMENT_COUNTER),
onDecrement: () => dispatch(DECREMENT_COUNTER)
};
}
let App = connect(
mapStateToProps,
mapDispatchToProps
)(Counter);
これでcountがコンポーネントのStateとして使用でき、
onIncrementとonDecrementがコンポーネントから呼び出せるようになります。
あとはサクッとコンポーネントを修正します。
class Counter extends React.Component {
render() {
const { count, onIncrement, onDecrement } = this.props;
return (
<div>
カウント: {count} 回
<br />
<button onClick={onIncrement}>プラス</button>
<button onClick={onDecrement}>マイナス</button>
</div>
)
}
}
最後にReactDOM.renderしたら完成です。
Providerでラップ、storeの指定、
コンポーネントをconnectした結果を指定することに注意。
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('wrapper')
);
今回作成したサンプルコード
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'
// Reactのみで構成したカウンター
class ReactCounter extends React.Component {
constructor(props) {
super(props);
this.state = {
value: 0
}
}
onIncrement() {
this.setState({ value: this.state.value + 1 });
}
onDecrement() {
this.setState({ value: this.state.value - 1 });
}
render() {
return (
<div>
カウント: {this.state.value} 回
<br />
<button onClick={::this.onIncrement}>プラス</button>
<button onClick={::this.onDecrement}>マイナス</button>
</div>
)
}
}
//ReactDOM.render(
// <ReactCounter />,
// document.getElementById('root')
//);
/* -- -- -- -- -- ここからRedux用コード -- -- -- -- -- */
// Redux向けに再構成したカウンター
class ReduxCounter extends React.Component {
render() {
const { count, onIncrement, onDecrement } = this.props
return (
<div>
{/* ReactCounterも一緒に描画してみる */}
<ReactCounter />
カウント: {count} 回
<br />
<button onClick={onIncrement}>プラス</button>
<button onClick={onDecrement}>マイナス</button>
</div>
)
}
}
// Actions
const INCREMENT_COUNTER = {
type: 'INCREMENT_COUNTER'
};
const DECREMENT_COUNTER = {
type: 'DECREMENT_COUNTER'
};
// Reducer
function counterReducer(state = {count: 0}, action) {
switch (action.type) {
case 'INCREMENT_COUNTER':
return {count: state.count + 1};
case 'DECREMENT_COUNTER':
return {count: state.count - 1};
default:
return state
}
}
// Store
const store = createStore(counterReducer);
function mapStateToProps(state) {
return {
count: state.count
};
}
function mapDispatchToProps(dispatch) {
return {
onIncrement: () => dispatch(INCREMENT_COUNTER),
onDecrement: () => dispatch(DECREMENT_COUNTER)
};
}
let ReduxCounterApp = connect(
mapStateToProps,
mapDispatchToProps
)(ReduxCounter);
// レンダリング
ReactDOM.render(
<Provider store={store}>
<ReduxCounterApp />
</Provider>,
document.getElementById('root')
);