前のパートに戻る 完了して次のパートへ  

  1-2 profile を管理する redux を作成しよう

このパートでは profileというフォームでそれぞれの入力内容を管理する redux を作成していきます。

本パートの目標物


このパートでは状態管理のロジックのみを作成するため成果物はありません。ビューを作成したときにまとめて動作確認を行います。

目標物を作成するまでの流れ


  1. ディレクトリ構成を理解しよう
  2. データ構造を定義しよう
  3. action を作成しよう
  4. reducer を作成しよう
  5. store をアプリケーションに登録しよう

この章では、簡単な項目の redux での状態管理のロジックを作成していきます。いきなり redux のロジック作成で大変ですが、頑張ってついてきてください!

redux でのデータの流れは以下のようなイメージです。

では実際に進めていきましょう。

1. ディレクトリ構成を理解しよう


今回 redux のディレクトリ構成として re-ducks パターンというものを採用しました。大げさな名前がついていますが、要は redux で管理する状態単位で module に分けていく構成のことです。言葉だけではわかりにくいので早速今回使うprofileを作成してみましょう。

まずは、このリポジトリのfront/に移動してください。そこで以下のコマンドを実行します。

$ mkdir -p src/store/profile
$ touch src/store/profile/actions.ts
$ touch src/store/profile/reducer.ts

これで以下のようなディレクトリ構成になったと思います。

.
├── components
│   ├── App.tsx
│   └── styles.ts
├── index.tsx
├── react-app-env.d.ts
└── store
    └── profile
        ├── actions.ts
        └── reducer.ts

react-app-env.d.ts は build 時に自動生成されるフィイルのため無視しておいてかまいません。

これでアプリケーションの store に、profile という状態を追加するための準備が整いました。追加で他の状態を実装したいときは、store/配下にvalidation/のような形で追加していきます。

└── store
    ├── profile
    |   ├── actions.ts
    |   └── reducer.ts
    └── validation
        ├── actions.ts
        └── reducer.ts

このようにすることでで store がprofilevalidationという状態を管理しているということがわかりやすくなります。

2. データ構造を定義しよう


続いてデータ構造を定義していきます。わざわざ定義するのは面倒に思うかもしれませんが、定義をしておくことで補完が効いたり誤った参照をしてエラーになることを未然に防いだりできるようになります。

では、まずはファイルを作成しましょう。設計で話したように、domain/entityというところにデータ構造を定義していきます。

$ mkdir -p src/domain/entity
$ touch src/domain/entity/profile.ts
$ touch src/domain/entity/gender.ts

これで以下のようなディレクトリ構成になります。

.
├── components
│   ├── App.tsx
│   └── styles.ts
├── domain
│   └── entity
│       └── gender.ts
│       └── profile.ts
├── index.tsx
├── react-app-env.d.ts
└── store
    └── profile
        ├── actions.ts
        └── reducer.ts

続いて、src/domain/entity/profile.tsを編集していきます。

項目は「名前」「自己紹介」「誕生日」としました。typeというシンタックスを使うことで任意のデータ構造を定義できます。主な用途としては、今回のようにオブジェクトのデータ構造を定義することに使用します。

ほかに型を定義する方法としてinterfaceがあります。ほとんど変わらないのですが、typeの方が少しだけ機能が充実しています。interfacetypeの使い分けが曖昧なため今回はすべてtypeに統一します。どちらが正解ということはないので実際の現場ではチームでの規約に従ってください。

ちなみにinterfaceを用いて記述すると以下のようになります。

続いて性別を作成します。性別は「男性」または「女性」としたいので、Genderというデータ構造を別途定義します。src/domain/entity/genderを編集していきます。

typeはパイプ(|)で区切ることでいづれかの型であるということを表現できます。また、型として文字列を定義した場合はその文字列のみを入れることができます。そのため、このGenderには、"male"または "female" または ""を入れることができるようになります。

では、profile.tsに性別を追加しましょう。

3. aciton を作成しよう


続いて action を作成していきます。src/store/profile/actions.tsを編集します。

たったこれだけで完了です。typescript-fsaを使うことで型情報を失わず簡単に定義することができます。actionCreatorにはジェネリクス(型引数)が使われています。setProfileという action のpayload(reducer に渡す値)の型をこれで定義することができます。

また、Partial<Profile>という記法ができました。これは、Profileという型の部分集合で、Profileの項目のうち必要なものだけを渡すことができます。含まれなかったProfileの項目はundefinedとして扱われます。そもそもProfileに含まれない項目を含んでいる場合はコンパイルエラーとなります。

input での入力を制御するときは、{ name: "入力された値" }{ gender: "male" }のように一つずつ更新していきたいので、Partial<Profile>としました。{ name: "入力された値", gender: "male" }とすることもできますが、今回はこのような複数の項目を同時に渡すような実装はありません。

reducer を作成しよう


続いて reducer を作成していきます。src/store/profile/reducer.tsを編集します。

少し、記述量が増えました。一つずつ確認していきましょう。

まずは、Profile型のinitという初期値を定義しています。redux では reducer で state の初期値を定義するのが一般的です。

const init: Profile = {
  name: "",
  description: "",
  birthday: "",
  gender: ""
};

次にそれをreducerWithInitialStateというtypescript-fsa-reducersの関数に渡しています。これで reducer が作成されました。

const profileReducer = reducerWithInitialState(init);

そしてそこに.case()をチェーンさせることでそれぞれのアクションでの処理を記述していっています。このcase()は第一引数にアクションを第二引数にコールバック関数を渡しています。この第二引数の関数の引数は、第一引数が直前のprofileという state そのもの、第二引数がアクションから渡ってきたpayloadとなっています。そして新しい state を return します。

今回は、payloadPartial<Profile>なので、もとのstateに今回の更新分を反映した新しいProfileを return しています。...はスプレッド演算子と呼ばれていて、オブジェクトや配列を展開することができます。また JavaScript では、Object 内に同じ項目があった場合は後の項目が優先されるので、payloadを後から展開することで、payloadの分だけ更新した新しい配列を返すことができます。

redux では直接値を更新してはいけないので、このような冗長なプロセスが必要になるのです。

5. store をアプリケーションに登録しよう


まずは、store を作成します。以下のファイルを作成してください。

$ touch src/store/index.ts
$ touch src/domain/entity/rootState.ts

src/domain/entity/rootState.tsに型定義をしていきます。

ここでは、store のデータ構造がどのようなものになっているのかを定義しています。新しく状態を追加した場合はsrc/store/index.tsに追記が必要です。

まずは、combineReducersという redux の API を用いて reudcer をひとつにまとめます。まとめた結果、先ほど定義したRootStateのような形になります。 それをcreateStoreという API に食わせることで store として動くようになります。

(window as any).__REDUX_DEVTOOLS_EXTENSION__ && (window as any).__REDUX_DEVTOOLS_EXTENSION__()は redux dev tools というものを使うための記述です。これに関しては、のちに使用します。

最後にアプリケーションに登録しましょう。

src/index.tsxを編集していきます。

Providerというコンポーネントでアプリケーション全体を包んでやることで store にアクセスできるようになります。このような store の作成はだいたいのアプリで同じになるのでこういうものなのだと思っておいていただいて ok です。

この章では、redux のフローを作成しました。いきなりヘビーな内容でしたが redux を扱う上で基本的な内容となっているため何度も読み返して理解してください。

議論

0 質問

このコースの評価は?