LoginSignup
26
22

More than 5 years have passed since last update.

ReactJS入門@ES6:Redux編

Last updated at Posted at 2016-02-08

対象者: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とは、命令コマンドのことだと思って下さい。
さて、このカウンターではどんな命令コマンドが必要でしょうか。

  1. カウントをアップせよ
  2. カウントをダウンせよ

まあこの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')
);
26
22
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
26
22