Skip to content
This repository has been archived by the owner on Sep 8, 2022. It is now read-only.

Step2:reduxのstoreから値を受け取り表示する

hiro-ueda edited this page Oct 25, 2018 · 4 revisions

reactではコンポーネントが様々な状態(state)を持つことによって動的にページが変化します。
redux-pluto ではその state を Redux によって管理しています。

今回はReduxによる state 管理によって動的なページを作成してみましょう。

Redux

state(状態)を管理をするためのフレームワークで主に以下の要素を持つ。

Action: 「何をする」という情報を持ったオブジェクト、type プロパティを必ず持つ
ActionCreator: Actionを作成するメソッド
Store: アプリケーションの状態(state)を保持している場所
State: アプリケーションの状態を表す
Reducer: actionとstateから、新しいstateを作成して返すメソッド
stete 管理の大雑把な概要は以下の通り。

state は Storeに保持されている。
state を更新したい場合は ActionCreator で Action を発行し、それを Store に dispatch することで reducer を走らせる。

Redux DevTools を Chrome に追加しておくと、Store の状態をリアルタイムに確認することができます。

Store に Reducer を追加する

redux module を作成する

STEP1で作成したコンポーネントで使うための state を管理する module を作成します。
ここではコンポーネント内の文言の表示・非表示の状態を管理する state と、それを切り替える reducer を作成していきます。
/shared/redux/modules 配下に以下の hello.js を追加しましょう。

shared/redux/modules/hello.js

+/* @flow */
+import { createAction, handleActions } from "redux-actions";
+
+ /**
+ * Action types
+ */
+const HELLO = "redux-pluto/hello";
+const HELLO_CHANGE_VISIBILITY = `${HELLO}/visibility/change`; // 表示・非表示を切り替える Action の type
+
+ /**
+ * Action creators
+ */
+export const changeVisibility = createAction(HELLO_CHANGE_VISIBILITY); // 表示・非表示を切り替える Action の  ActionCreator
+ 
+ /**
+ * Initial state
+ */
+ // module 内で管理する state の型
+ export type State = {
+  isVisible: boolean,
+};
+
+ // store に展開される初期値
+ const INITIAL_STATE = {
+  isVisible: true,
+};
+
+ /**
+ * Reducer
+ */
+export default handleActions(
+  {
+    [HELLO_CHANGE_VISIBILITY]: state => ({ // HELLO_CHANGE_VISIBILITY が dispatch された時に走る reducer
+      ...state,
+      isVisible: !state.isVisible,
+    }),
+  },
+  INITIAL_STATE,
+);

利用module

redux-actions
createActions: 引数に action type をとり ActionCreators を返す
handleActions: 第一引数に reducerMap 第二引数に defaultState をとり複数の reducer を作成、それらを複数の action を処理する単一の reducer にまとめてくれる

作成した module の reducer を親 Reducer に追加する

/shared/redux/modules/reducer.js に 先ほど作成した hello.js をインポートして、 ページスコープのReducer(page配下)に追加します。
reducer は app と page にわかれており、それぞれアプリケーション全体で使われるものか、該当ページにのみ利用されるかのスコープによって使い分けます。

shared/redux/modules/reducer.js

~~~
+ import hello, { type State as Hello } from "./hello";
~~~
export type State = {
~~~
  page: {
+    hello: Hello,
~~~  

export default combineReducers({
~~~
 page: pageScopeReducer(
   combineReducers({
+     hello,
~~~

利用module

redux-page-scope
historyEvent に応じた react-router の Action を見て state を初期化したり cache したりする 特定の page に閉じている state に利用する

ここまでできたらRedux DevTools を確認してみましょう。
先ほど作成した module の state が反映されていることがわかります。

画面に store の値を反映する

component に redux store の値を渡す

Store の内容を画面に表示するには、React コンポーネントの中で Store の持つ state をコンポーネントの props として反映させる必要があります。
これには react-reduxconnect を利用します。

react-redux connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
文字通り react componentとreduxのstoreをつなぐために利用される。

mapStateToProps(state, ownProps) store.getState()の結果を第一引数,Container componentへ渡されたpropsを第二引数にして呼び出される関数 これらのstateとpropsを使ってPresentational componentにpropsとして渡す値を返す

mapDispatchToProps(dispatch) store.dispatchを第一引数にして呼び出される関数 Presentational componentにpropsとして渡す store に dispatch して state を更新するための関数を返す

src/shared/components/organisms/Hello/index.js

+/* @flow */
 import React from "react";
+import { compose } from "recompose";
+import { connect } from "react-redux";
+import { changeVisibility } from "../../../redux/modules/hello";

-export default function Hello(props) {
-  return <div>Hello!</div>;
-}
+type Props = {  // props の型定義
+  isVisible: boolean,
+  onChangeVisibility: Function,
+};
+
+export default compose(
+  connect(
+    state => ({
+      isVisible: state.page.hello.isVisible, // store の state の中から、指定した isVisible を props として渡す
+    }),
+    dispatch => ({
+      onChangeVisibility: () => dispatch(changeVisibility()), // changeVisibilityを store に dispatchする関数を返す
+    }),
+  ),
+)(function Hello(props: Props) {
+  const { isVisible, onChangeVisibility } = props;
+  return (
+    <div>
+      {isVisible && <div>Hello!</div>}
+      <button type="button" onClick={() => onChangeVisibility()}>
+        {isVisible ? "hide" : "show"}
+      </button>
+    </div>
+  );
+});

recompose

Higher-order Componentsを作成・提供するための便利関数ライブラリ compose(...Higher-order components)(EnhancedComponent)を用いることで EnhancedComponentが...Higher-order Componentsで拡張されたComponentになる ※Higher Order Component(HOC)とは、単に他のコンポーネントをラップするReactコンポーネントのことで、 HOCにより以下のことが可能になる

  • コードの再利用、ロジックの抽象化
  • Stateの抽象化と操作
  • Propsの操作

動作確認

http://localhost:3000/helloを開くと、
Hello!の下に「hide」ボタンが追加されているのが確認できます。
「hide」ボタンを押して動作を確認してみましょう。
Hello! の表示が消え、ボタンのラベルが「show」に変化すれば成功です!

Container component と Presentational component に分離する

このままでも問題なく動きますが、
画面表示のロジックと、データ受け渡し用のロジックが1つのファイルに混在している状態は好ましくないため、それぞれを別のコンポーネントとして分けて作成します。

まずは 先ほどの index.js から画面表示部分だけ切り出した component Hello.js を作成しましょう src/shared/components/organisms/Hello/Hello.js

+/* @flow */
+import React from "react";
+
+type Props = {
+  isVisible: boolean,
+  onChangeVisibility: Function,
+};
+
+export default function Hello(props: Props) {
+  const { isVisible, onChangeVisibility } = props;
+  return (
+    <div>
+      {isVisible && <div>Hello!</div>}
+      <button type="button" onClick={() => onChangeVisibility()}>
+        {isVisible ? "hide" : "show"}
+      </button>
+    </div>
+  );
+}

次に index.js をデータの受け渡しやロジックに専念する Container component にしましょう。 src/shared/components/organisms/Hello/index.js

-/* @flow */
-import React from "react";
 import { compose } from "recompose";
 import { connect } from "react-redux";
 import { changeVisibility } from "../../../redux/modules/hello";
-
-type Props = {
-  isVisible: boolean,
-  onChangeVisibility: Function,
-};
+import Hello from "./Hello";
~~~
-)(function Hello(props: Props) {
-  const { isVisible, onChangeVisibility } = props;
-  return (
-    <div>
-      {isVisible && <div>Hello!</div>}
-      <button type="button" onClick={() => onChangeVisibility()}>
-        {isVisible ? "hide" : "show"}
-      </button>
-    </div>
-  );
-});
+)(Hello);

問題なく画面が動いているようであれば次のSTEPへ進みましょう!

このSTEPのソースコード