読者です 読者をやめる 読者になる 読者になる

Nukindex 開発ブログ

アダルトサイトNukindexの開発ブログです

初心者によるReduxのふわっとしたイメージ解説

Redux + React に挑戦している。 なんとなく感覚はつかめてきたが小さい謎が多い。 感覚をつかむまでの苦しみがすごいのでメモを残しておきたい。

実際の Redux のライフサイクル

f:id:katz_ura:20170427000805p:plain

イメージ解説:「りんごがひとつ売れたので在庫情報を更新したい」

f:id:katz_ura:20170426234316p:plain

ユーザーさん(View):「りんごがひとつ売れたので在庫情報を更新したい」

f:id:katz_ura:20170426234601p:plain

受付さん(Action Creator):「わかりました。少し待っててくださいね。φ(・_・”)」

受付さん(Action Creator):「お待たせしました。これを事務さんに持っていってください。」

f:id:katz_ura:20170426234640p:plain

依頼書(Action):「【業務】りんごが売れたため在庫更新【個数】1」

f:id:katz_ura:20170426235023p:plain

事務さん(Reducer):「りんごが売れたため在庫更新、個数は1、ですね。わかりました。」

f:id:katz_ura:20170426235213p:plain

事務さん(Reducer):「書類棚から在庫管理表を取ってきて、」

f:id:katz_ura:20170426235504p:plain

  • 書類棚(Store)
  • 在庫管理表(State)

f:id:katz_ura:20170427000420p:plain

事務さん(Reducer):「新しい在庫管理表と依頼書を照らし合わせて新しい在庫管理表を作る。」

f:id:katz_ura:20170427000631p:plain

事務さん(Reducer):「それでは在庫更新しておきましたのでもう大丈夫ですよ。」

f:id:katz_ura:20170426234316p:plain

ユーザーさん(View):「ありがとうございました。」

テスト用プロジェクト

ROOT_DIRECTORY/
  + node_modules/
  + build/
  + source/
  |   + renderer/
  |   |   + components/
  |   |       + app/
  |   |           + container.js
  |   |           + view.js
  |   |           + reducer.js
  |   |           + actions.js
  |   |
  |   + index.html
  |
  + gulpfile.babel.js
  + .babelrc
  + package.json

source/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Redux Sample</title>
</head>
<body>
  <div id="app"></div>

  <script src="./renderer/bundle.js"></script>
</body>
</html>

source/renderer/components/app/container.js

connect は Redux と React をつなげる。 mapStateToProps は Redux で管理する state を React コンポーネントの props にひもづける。 mapDispatchToProps は Action Creator への呼び出しを React コンポーネントの props にひもづける。

React コンポーネントでは state ではなく props を使っていくことになる。

自分でも書いててよく分からんけど何回か書いてたら慣れる。

import React from "react";
import { connect } from "react-redux";
import Component from "./view";
import Actions from "./actions";

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Component);

function mapStateToProps(state) {
  console.log("container.js: mapStateToProps\n" +
      "state: " + JSON.stringify(state, null, "  "));

  return state.app;
}

function mapDispatchToProps(dispatch) {
  console.log("container.js: mapDispatchToProps");

  return {
    "sellHandler": () => {
      dispatch(Actions.sell());
    },
    "recieveHandler": () => {
      dispatch(Actions.recieve());
    }
  };
}

source/renderer/components/app/view.js

React のコンポーネント

import React from "react";

export default class Component extends React.Component {
  render() {
    console.log("view.js: render\n" +
        "state: " + JSON.stringify(this.state, null, "  ") + "\n" +
        "props: " + JSON.stringify(this.props, null, "  "));

    return (
      <div>
        <table>
          <thead>
            <tr>
              <th>品名</th>
              <th>単価</th>
              <th>在庫</th>
            </tr>
          </thead>
          <tbody>
            <tr>
              <td>{this.props.product.name}</td>
              <td>{this.props.product.price}</td>
              <td>{this.props.product.stock}</td>
            </tr>
          </tbody>
        </table>

        <button onClick={this.props.sellHandler}>売る</button>

        <button onClick={this.props.recieveHandler}>入荷する</button>
      </div>
    );
  }
}

source/renderer/components/app/reducer.js

Action による state の変更を管理する。state の初期値もここで設定する。事務さん。

const initialState = {
  "product": {
    "name": "りんご",
    "price": 120,
    "stock": 100
  }
};

export default function reducer(state = initialState, action) {
  console.log("reducer.js: reducer\n" +
      "state: " + JSON.stringify(state, null, "  ") + "\n" +
      "action: " + JSON.stringify(action, null, "  "));

  switch(action.type) {
    case "PRODUCT_SOLD":
      return Object.assign({}, state, {
        "product": {
          "name": state.product.name,
          "price": state.product.price - action.amount,
          "stock": state.product.stock
        }
      });

    case "PRODUCT_RECIEVED":
      return Object.assign({}, state, {
        "product": {
          "name": state.product.name,
          "price": state.product.price + action.amount,
          "stock": state.product.stock
        }
      });

    default:
      return state;
  }
}

source/renderer/components/app/actions.js

Action を返し、state の変更パターンを定義する。受付さん。

export default {
  "sell": () => {
    console.log("actions.js: sell");

    return {
      "type": "PRODUCT_SOLD",
      "amount": 1
    };
  },
  "recieve": () => {
    console.log("actions.js: recieve");

    return {
      "type": "PRODUCT_RECIEVED",
      "amount": 1
    }
  }
}

ログつきテストページ

デバッガーのコンソールタブにログがでる。

テストページを開く

ページ読み込み時のログ

初期描画時に謎に Reducer が 3回も動いている。謎でしかない。

reducer.js: reducer
state: {
  "product": {
    "name": "りんご",
    "price": 120,
    "stock": 100
  }
}
action: {
  "type": "@@redux/INIT"
}


reducer.js: reducer
state: {
  "product": {
    "name": "りんご",
    "price": 120,
    "stock": 100
  }
}
action: {
  "type": "@@redux/PROBE_UNKNOWN_ACTION_v.1.7.7.f.r"
}


reducer.js: reducer
state: {
  "product": {
    "name": "りんご",
    "price": 120,
    "stock": 100
  }
}
action: {
  "type": "@@redux/INIT"
}


container.js: mapStateToProps
state: {
  "app": {
    "product": {
      "name": "りんご",
      "price": 120,
      "stock": 100
    }
  }
}


container.js: mapDispatchToProps


view.js: render
state: null
props: {
  "product": {
    "name": "りんご",
    "price": 120,
    "stock": 100
  }
}

「売る」ボタンを押したときのログ

Action Creator => Reducer の流れで state の値が変化していること、 state 変化後に render が呼ばれていることがわかる。

  • stock を変えたつもりが price が変わっている。凡ミス。
  • mapStateToProps って毎回呼ばれるのね。 props にひもづけたらお役御免かと思っていた。
actions.js: sell


reducer.js: reducer
state: {
  "product": {
    "name": "りんご",
    "price": 120,
    "stock": 100
  }
}
action: {
  "type": "PRODUCT_SOLD",
  "amount": 1
}


container.js: mapStateToProps
state: {
  "app": {
    "product": {
      "name": "りんご",
      "price": 119,
      "stock": 100
    }
  }
}


view.js: render
state: null
props: {
  "product": {
    "name": "りんご",
    "price": 119,
    "stock": 100
  }
}

「入荷する」ボタンを押したときのログ

Action Creator => Reducer の流れで state の値が変化していること、 state 変化後に render が呼ばれていることがわかる。

actions.js: recieve


reducer.js: reducer
state: {
  "product": {
    "name": "りんご",
    "price": 120,
    "stock": 100
  }
}
action: {
  "type": "PRODUCT_RECIEVED",
  "amount": 1
}


container.js: mapStateToProps
state: {
  "app": {
    "product": {
      "name": "りんご",
      "price": 121,
      "stock": 100
    }
  }
}


view.js: render
state: null
props: {
  "product": {
    "name": "りんご",
    "price": 121,
    "stock": 100
  }
}

nukindex.com