LoginSignup
222
187

More than 3 years have passed since last update.

コードで理解するRedux(React使用)

Last updated at Posted at 2016-12-22

対象読者

  • Reactの書き方がある程度はわかってる人
  • Reduxの概念とか言われてもわからない、コードでくれって人
  • 私(備忘録)

Reduxとは

fluxの考え方にもとづいて作られたフレームワーク
React専用ではないが、Reactと連携させることでより高いパフォーマンスを得られる

【参考】fluxとは

facebookが提唱したアーキテクチャ(ソフトウェア設計の思想・考え方)
データの流れを一方通行にしたもの
flux流れ.png

Reduxの特徴

Reduxの3原則
1.Single source of truth
 1つのアプリケーションが持つstateは1つのみ
 stateをオブジェクトツリー構造で管理する

2.State is read-only
 stateを直接変更することはできない
 変更したいときはActionをdispatchで渡したときのみ

 これにより、stateの変更に関わる部分が限定化され、バグの特定が容易になる

3.Changes are made with pure functions
 reducerは純粋関数である
 同じ引数を渡されたら同じデータを返す

 ランダム値や現在の日付をReducer内で取得しない

サンプル

プログラム概要

入力した内容の税込(8%)の金額を表示させるだけのプログラム

aaa.gif

reduxのイベント発生後の流れ

2016-12-26_115934.png

View

2016-12-26_120430.png
表示させる部分で、イベントの発火位置でもある


Presentational Components

いままでのReactコンポーネントのこと

ボタンクリックで、Inputに入力した値を引数としてpropsとして受け取ったonClick関数を実行する
class AppComponents extends React.Component {

  send(e){
    this.props.onClick(this.refs.inputText.value);
  }


  render() {
    return (
      <div>
        <input type="text" defaultValue="" ref="inputText" /> { /* 入力フォーム */ }
        <button onClick={this.send.bind(this)}>計算</button>  { /* ボタン */ }
        <br />
        {this.props.price}  { /* 表示させる税込の金額 */ }
      </div>
    );
  }
}

Container Components

Reduxと連携するコンポーネント
正直ここが一番ややこしい

PresentationalComponentsにpriceのStateとonClickの関数を渡す
function mapStateToProps(state) {
  return {
    price: state.price
  };
}

function mapDispatchToProps(dispatch) {
  return {
    onClick(price){
      dispatch(addTax(price));
    }
  };
}

let AppContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(AppComponents);

mapStateToProps

Presentational Componentsに渡すStateとしてpriceを指定している

mapDispatchToProps

Presentational Componentsに渡す関数としてonClickを指定している
addTax(price)はActionCreator(後述)の関数でAction(オブジェクト)が返ってくる
dispatch(Action)を実行することでReducer(後述)が実行される

また、bindActionCreatorsを使用することで、ActionCreatorの関数をdispatchせずに実行できる

bindActionCreatorsの例
import * as Actions from './../actions/app';

const mapDispatchToProps = (dispatch) => (bindActionCreators(Actions, dispatch));

connect

React-ReduxのconnectメソッドでReactとReduxをつなげている

1つ目の引数に[Stateを返す関数][Stateを変更する関数を書いた関数]を、
2つ目の引数に[Presentational Componentsのトップのコンポーネント]を指定する

上記のように書くことでPresentational Componentsのトップのコンポーネントでthis.props.priceや'this.props.onClick'のように使用できるようになる

ActionCreator

2016-12-22_182655.png

どのアクションなのかをReducerに判別させるため、typeという要素をつける
typeはconstで定数化するのがツウらしい

受け取ったpriceをActionにして返す関数
const ADDTAX = 'ADDTAX';
function addTax(price) {
  return {
    type: ADDTAX,
    price
  };
}

Reducer

2016-12-22_182622.png

Actionで指定したtype要素をswitch文で判別し、関数を実行する
引数にStateとActionを指定する

Actionのpriceを1.08倍して現在のStateにマージし、新しいStateとして返す関数
function appReducer(state, action) {
  switch (action.type) {
    case 'ADDTAX':
      return (
        Object.assign({}, state, {price: action.price * 1.08})
      );
    default:
      return state
  }
}

初期処理とレンダリング

storeを作成し、レンダリングする
//state初期化
const initialState = {
  price: ''
};

//store作成
const store = createStore(appReducer, initialState);

//レンダリング
ReactDOM.render(
  <Provider store={store}>
    <AppContainer />
  </Provider>,
  document.getElementById('root')
);

createStore

ReducerとState初期値を渡してStoreを作成する
そのStoreをView(Container Components)に渡すことでReactでstoreを使える

Reducerを複数に分けたい場合はcombineReducersを使う

combineReducersの例
const store = createStore(
  combineReducer(FirstReducer, SecondReducer)
)

Provider

storeプロパティに先程作ったstoreを入れる
Providerコンポーネントの子コンポーネントとしてView(Container Components)を指定する

コード全体

実行にはreduxとreact-reduxのインストールが必要

npm install --save redux
npm install --save react-redux
import React from 'react'
import ReactDOM from 'react-dom'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'

//Presentational Components
class AppComponents extends React.Component {

  send(e){
    this.props.onClick(this.refs.inputText.value);
  }


  render() {
    return (
      <div>
        <input type="text" defaultValue="" ref="inputText" /> { /* 入力フォーム */ }
        <button onClick={this.send.bind(this)}>計算</button>  { /* ボタン */ }
        <br />
        {this.props.price}  { /* 表示させる税込の金額 */ }
      </div>
    );
  }
}

function mapStateToProps(state) {
  return {
    price: state.price
  };
}

function mapDispatchToProps(dispatch) {
  return {
    onClick(price){
      dispatch(addTax(price));
    }
  };
}

let AppContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(AppComponents);



// ActionCreator
const ADDTAX = 'ADDTAX';
function addTax(price) {
  return {
    type: ADDTAX,
    price
  };
}


// Reducer
function appReducer(state, action) {
  switch (action.type) {
    case 'ADDTAX':
      return (
        Object.assign({}, state, {price: action.price * 1.08})
      );
    default:
      return state
  }
}


//state初期化
const initialState = {
  price: ''
};

//store作成
const store = createStore(appReducer, initialState);

//レンダリング
ReactDOM.render(
  <Provider store={store}>
    <AppContainer />
  </Provider>,
  document.getElementById('root')
);

コードで見るイベント発生後の流れ

2016-12-26_115934.png
上にも貼ったこの画像で処理の流れをコードで見ていきます。

①【計算】ボタンをクリックしたときContainer Componentsから受け取った関数を実行する

2016-12-26_124731.png

②ActionCreatorの関数を実行する

2016-12-26_125158.png

③入力金額が入ったAction(オブジェクト)を返す

2016-12-22_174410.png

④dispatch(Action)を実行する 実行すると勝手にReducerが動く

2016-12-22_174710.png

⑤⑥新しいStateになったのでViewに渡している値も変わる

2016-12-26_130916.png

参考にしたサイト

http://mae.chab.in/archives/2885
http://qiita.com/kiita312/items/49a1f03445b19cf407b7
http://qiita.com/gcfuji/items/547ab425f96bf7134b9d

222
187
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
222
187