More than 1 year has passed since last update.

背景

reduxとnormalizr使ってます、という話を勉強会でした時に、いまいち実装イメージが思い浮かばないというフィードバックをもらったので、TODOアプリで例を作ってみた。

ソースはこちら:https://github.com/hokuma/redux-normalizr-todo

TL;DR

  • APIのレスポンスでリソースがネストしている時に効果的
  • entities(ローカルDB)とresult(データの集合)でreducerを分ける
  • entitiesはImmutable.Record使う(おまけ)

題材

よくあるTODOアプリだとリソースはTODOしか出てこない。リソースが一つしかなければそもそもReact単体でやったら、という話になるので、TODOを実行する人をアサインできる機能を持ったTODOアプリを題材にした。

書くこと

reducerの切り方、normalizrの使い方、entityの持ち方。

書かないこと

action周り。

リソース

扱うリソースは以下の通り。

/todos.json
{
  "todos":[
    {
      "todo":{
        "id":1,
        "body":"ticket #1",
        "status":1,
        "userId": 1,
        "user":{
          "id":1,
          "name":"john"
        }
      }
    }
  ]
}
/users.json
{
  "users":[
    {
      "user":{
        "id":1,
        "name":"john",
        "status":1
      }
    }
  ]
}

クライアント側での描画待ちを減らしたい、などの理由で一つのAPIレスポンス内でリソースがネストするようなケースは実際あるだろう。

normalizr

https://github.com/gaearon/normalizr

リンク先を見ればわかるが、ネストしたjsonをentityとresultに分解してくれるライブラリ。

entityはデータの実体で、データベースの1レコードに相当すると考えれば良い。resultは意味を持ったデータの集合だが、実データは持たずentityへの参照のみを持つ。この辺は説明よりもリンク先の例を見た方がわかりやすい。

実体とそれへの参照に分けるところがポイントになる。

One Fact in One Place

をクライアント側でもやる感じ。

reducer

entitiy用、result用のreducerを作る。entity用はユーザ用、TODO用とそれぞれネストしていく。result用も同様で、ユーザ一覧、TODO一覧用とネストする。

  • entitiy用のreducerはクライアント側のテーブル操作を行うreducer
  • result用のreducerはアプリとして見せたい情報のまとまり(ユーザ一覧、TODO一覧)操作を行うreducer

と見ることができる。

entities

entities/以下にそれぞれのリソースごとに定義する。

アクションごとに適用するデータ処理を定義する。データ取得系では、基本的にはサーバから受け取った値を最新の値とみなし、既存データを更新 or なければローカルのstoteに追加する。これをまとめてmerge処理として実装する。

状態更新系では、例えばfinishのような状態変化の操作を定義し、対象のtodoのstatusを更新する。

result

entities/以外のreducerは、基本的にはresultを処理するreducerとなる。

取得したデータ or 追加したデータのentityIdをリストへマージするのが主な役割。finishのようなentityそのものを変える処理は扱わない(entityのreducerで処理する)。

Immutable.js

entity、resultどちらもImmutable.jsで管理する。

オブジェクトや集合を扱うためのAPIが豊富で、かつ実行結果が新しいオブジェクトになるのでデータ更新の副作用に起因するバグを減らせる。

データ型がすでにいくつか定義されており、使いようによってはなかなか便利。例えば、resultでOrderedSetを使っている。Setなので同一の値は勝手にユニークが取られ、さらにOrdered、つまり追加した順番を保持してくれるので、一覧表示のためのデータを管理するのに都合が良い。

Immutable.Record

Immutable.Recordを使って、Immutableのインスタンスをクラスのインスタンスっぽく使うこともできる。できることは以下の3つ。

  • 取りうる属性とdefault値を定義できる
  • 属性名でアクセスできる(getを使わなくて良い)
  • 任意のgetterを定義できる

特に2つ目と3つ目のおかげで、Immutable.jsを使いつつも普通のオブジェクトっぽく使えるので、Component内からのアクセスが簡潔になる。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.