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

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

Tomohide Takao edited this page Apr 1, 2019 · 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.ts を追加しましょう。

shared/redux/modules/hello.ts

+/**
+ * Action types
+ */
+const HELLO_CHANGE_VISIBILITY = "redux-pluto/hello/visibility/change"; // 表示・非表示を切り替える Action の type
+
+type ChangeVisibility = {
+  type: typeof HELLO_CHANGE_VISIBILITY;
+};
+
+type Action = ChangeVisibility;
+
+/**
+ * Action creators
+ */
+export function changeVisibility() {
+  return {
+    type: HELLO_CHANGE_VISIBILITY,
+  };
+}
+
+/**
+ * Initial state
+ */
+// module 内で管理する state の型
+export type State = {
+  isVisible: boolean;
+};
+
+// store に展開される初期値
+const INITIAL_STATE = {
+  isVisible: true,
+};
+
+/**
+ * Reducer
+ */
+export default function(state: State = INITIAL_STATE, action: Action): State {
+  switch (action.type) {
+    case HELLO_CHANGE_VISIBILITY: {
+      return {
+        ...state,
+        isVisible: !state.isVisible,
+      };
+    }
+    default: {
+      return state;
+    }
+  }
+}
+

利用module

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

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

/shared/redux/modules/reducer.ts に 先ほど作成した hello.js をインポートして、 アプリスコープのReducer(app配下)に追加します。

shared/redux/modules/reducer.ts

~~~
+ import hello, { State as Hello } from "./hello";
~~~
export type RootState = {
~~~
  app: {
+    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.ts

import React from "react";
+import { compose } from "redux";
+import { connect } from "react-redux";
+import { changeVisibility } from "../../../redux/modules/hello";
+import { RootState } from "../../../redux/modules/reducer";

-export default function Hello() {
-  return <div>Hello</div>;
-}
+type Props = {
+  // props の型定義
+  isVisible: boolean;
+  onChangeVisibility: Function;
+};
+ 
+export default compose(
+  connect(
+    (state: RootState) => ({
+      isVisible: state.app.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.tsx を作成しましょう src/shared/components/organisms/Hello/Hello.tsx

+import React from "react";
+
+export type Props = {
+  // 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.ts(index.tsxからリネーム)

-import React from "react";
import { compose } from "redux";
import { connect } from "react-redux";
import { changeVisibility } from "../../../redux/modules/hello";
import { RootState } from "../../../redux/modules/reducer";
+import Hello from "./Hello";
-
-type Props = {
-  // props の型定義
-  isVisible: boolean;
-  onChangeVisibility: Function;
-};

export default compose(
  connect(
      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>
-  );
-});
+)(Hello);

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

このSTEPのソースコード