es6
React
redux
OriginalAtraeDay 1

ReactにReduxを導入する手順

1日目なのでかるーい内容にします笑。

最近Reactで開発していたのですが、状態管理がはちゃめちゃになって二度と読みたくないコードと化してしまいそうだったのでreduxを入れてみました。
今日だと多くの記事を見かけますが「開発途中で書き直した」みたいなものって意外となかったのでキャッチアップとかあとでするから今はとりあえず早く入れてみたい!と思った人向けに書こうと思います。(実際動いているのを見ながらの方がしっくりきたりしますし・・・)

導入方法

ライブラリをインストールする。

この辺りはサクッといきます。僕はnpm使っているのでnpmで記述します。
必要なライブラリは「ReduxとReact-Redux」なので各々installします。

# Redux
npm install redux

# React-Redux
npm install react-redux

これでおわりです。では実際に導入してみましょう。

コード(React編)

こういうコードがあったとします(チェックボックスを一つ用意しているだけのviewです)
適当に書いたのでもしかしたらミスっているかもですが・・

/main.js.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import Test from './src/test';

try{
  ReactDOM.render(
    <Test />,
    document.querySelector('#js-react-main')
  );
}catch(e){
  throw new Error("RenderFailed");
}

/src/test.js.jsx
import React from 'react';
import {Checkbox} from 'react-mdl';

export default class Test extends React.Component {
  constructor(props) {
      super(props);
     this.state = {
         checked: false
     }
     this.changeCheckedState = this.changeCheckedState.bind(this)
  }

  render() {
    return(
      <Checkbox id="test" label="test" checked={this.state.checked} onChange={this.changeCheckedState}/>
    )
  }

  changeCheckedState() {
    this.setState({
      checked: !this.state.checked
    })
  } 
}


コード(Redux導入編)

reduxはstroe、action、reducerというので役割分けて管理するのでまずはそのファイル作っていきます。僕はcontainersを挟むようにしているのでこんな感じになります。

/main.js.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';//react-reduxの機能
import configureStore from '../store/store'; //ファイル追加①
import Container from './containers/container';//ファイル追加①

const store = configureStore();

try{
  ReactDOM.render(
    <Provider store={store}>
      <Container />
    </Provider>,
    document.querySelector('#js-react-search-main')
  );
}catch(e){
  throw new Error("RenderFailed");
}

以下ファイル追加①の分を記述

/stores/store.js
import {createStore,  applyMiddleware} from 'redux';//reduxの機能
import Reducer from './reducers/reducer'; //ファイル追加②
import {createLogger} from 'redux-logger';//ログを出力したいので入れてます

export default function configureStore() {
  const logger = createLogger({logger:console});
  const createStoreWithMiddleware = applyMiddleware(logger)(createStore);
  const store = createStoreWithMiddleware(Reducer);
  return store;
}
/containers/container.js
import { bindActionCreators } from 'redux';//reduxの機能
import { connect } from 'react-redux';//react-reduxの機能
import Test from './src/test';
import * as Actions from './actions/action'; //ファイル追加②

function mapStateToProps(state) {
  return state;
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(Actions, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(Test)

以下ファイル追加②の分を記述

/actions/action.js
//返り値でtypeを返すようにします

export function test() {
  return {
    type: "test"
  };
}
/reducers/reducer.js
//actionで返したtypeを元にswitch文を記述します

const initialState = {
}

export default function Reducer(state=initialState, action) {
  switch(action.type){
    case "test":
      return state
    default:
      return state;
  }
}

ここまでがreduxのための準備です。
あとはreact部分を書き換えていきましょう!

ざっくり大きく2つのことを頭に入れておけば手が進むと思います
- stateは使わない。何か呼び時は基本的にpropsを使用
- 状態変化であるsetState({})も使わないでpropsでactionsの関数を呼ぶ

/src/test.js.jsx
import React from 'react';

export default class Test extends React.Component {
  render() {
    return(
      <Checkbox id="test" label="test" checked={this.props.checked} onChange={this.changeCheckedState.bind(this)}/>
    )
  }

  changeCheckedState() {
    this.props.changeCheckedFunction(this.props.checked)
  } 
}

actionとreducerを調整していきます。

/actions/action.js
//this.props.changeCheckedFunctionとしているのでその関数を用意、引数も入れてます

export function changeCheckedFunction(currentState) {
  return {
    type: "changeCheckedState",
    currentState: currentState
  };
}
/reducers/reducer.js
//constructorで定義していた初期値設定

const initialState = {
  checked: false
}

//actuonで定義したtypeを元に調整

export default function Reducer(state=initialState, action) {
  switch(action.type){
    case "changeCheckedState":
      return Object.assign({}, state, {
        checked: !action.currentState
      })
    default:
      return state;
  }
}

これで完了です。(動くか試していないのでもしかしたらエラーになっているかもしれません)
0からreduxを導入しようとすると最初ファイル追加したり記述したりと前準備があり、めんどくさいのですがと一旦準備してしまえばあとはほとんどactionとreducerに書いていくだけなので楽です(最初は頑張りましょう!)

「reduxってなんとなくわかっているけどなんかなー」と思っている人はこの前準備の所で???となっている気がするのでそこら辺を説明していこうかなと思います。

仕組み

仕組みというよりかはデータがどう流れているのかを頭の整理がてら文章化してみます。
document読めば確実なので読んでください・・・https://redux.js.org/

Reduxの要素

以下の要素で構成されています。

  • Action
  • ActionCreator
  • Store
  • Reducer

全体感

こんな感じでしょうか
スクリーンショット 2017-12-01 18.11.19.png
縦に3つ並んでいるところがreduxの部分、componentがreact部分という理解です。

actionを書き忘れているようにみえるのですが、ちゃんと存在しています。
矢印の中にいるという認識でも良いかもしれないですね。
actionはActionCreatorによって生成されたオブジェクトです。
生成されたオブジェクトをdispatchという関数を使ってstoreに送っています。

各機能の役割について

この記事が結構わかりやすいと思います!
Redux入門【ダイジェスト版】10分で理解するReduxの基礎

  • Actionは「何をする」という情報を持ったオブジェクトです。(Actionはtypeプロパティを必ず持つ必要があります)
  • ActionCreatorはActionを作成するメソッドです。Actionを作るのみを行いStoreへの送信は行ないません。
  • StoreのインスタンスにActionを送信する事でStoreへ変更を伝えます。
  • Storeはアプリケーションの状態(state)を保持している場所で。stateを更新するためのdispatch(action)を提供しています。
  • Storeは、Storeを作成する際にStateを変更するためのメソッドであるReducerを一つ登録します。 Storeはdispatchされると、引数のactionと現在保持しているStateをReducerへ渡し、新しいStateを作成します。
  • reducerはactionとstateから、新しいstateを作成して返すメソッドです。ポイントは、引数のstateを更新することはせず、新しいstateのオブジェクトを作成して返します。
  • Reducerによって新しいStateが作成されるので、Storeは現在のStateに代わり保持します。

簡単に振り返る

冒頭でチェックボックスのコードを書きましたがそれを参考にしてみます

  • チェックボックスをチェックする
  • チェックされると関数が実行される(changeCheckedState)
  • { connect } from 'react-redux' としてconnectを使うと ActionCreator と同じ形の functionで自動的に発行してくれる → つまりcomponentから直接actionが呼べます。今回はそれをcontainerで宣言して使っています)
  • actionが実行されます(changeCheckedFunction)
  • actionでは{type: "changeCheckedState",currentState: currentState}というオブジェクトを返しています
  • action実行とともにreducerが起動し新しいstateを発行します。
  • stateが変更されたので新しいstateでviewに渡ります。

まとめ

文章にしたりすると結構複雑なことしているのかな?と思われますが実際やってみるとそこまで複雑ではないのでぜひ興味ある方、まだredux触れてないって方はやってみてはいかがでしょうか?
本当は「肥大化した時のファイル分割構成とか呼び出し」とか「Middleware部分」とかの部分までやりたかったのですが、内容ないのになぜか長くなってしまったので今回はここまでにします。