-
Notifications
You must be signed in to change notification settings - Fork 18
Step2:reduxのstoreから値を受け取り表示する
reactではコンポーネントが様々な状態(state)を持つことによって動的にページが変化します。
redux-pluto ではその state を Redux によって管理しています。
今回はReduxによる state 管理によって動的なページを作成してみましょう。
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 の状態をリアルタイムに確認することができます。
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 にまとめてくれる
/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 の内容を画面に表示するには、React コンポーネントの中で Store の持つ state をコンポーネントの props として反映させる必要があります。
これには react-redux の connect を利用します。
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>
+ );
});
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」に変化すれば成功です!
このままでも問題なく動きますが、
画面表示のロジックと、データ受け渡し用のロジックが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へ進みましょう!