Edited at

normalizrを使ったreduxプロジェクト

More than 3 years have 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内からのアクセスが簡潔になる。