コレクションのクライアント側トラッキングについて
Reactでコレクションの複数のアイテムをまとめて編集したい。
リスト系扱うだけならEntityAdaptorやRTK Query(こっちは試してない)などありそうですが、
いまいち要件を満たせてない(単に知らないだけかも)ので自分で作ってみました。
.NETのEntityFrameworkやPHPのDoctrineっぽい感じのローカル用の簡易ライブラリです。
アソシエーションとか一切考慮してません。
DBと逐次連動するのではなく、
フォームで一通りの編集(追加、削除、変更)を終えてまとめてDBに反映することを想定してます。
おおまかな機能
コレクションを編集する際フォームで行う作業は、
- 新しいアイテムをコレクションに追加する
- コレクションからアイテムを削除する
- アイテムの内容を変更する
があります。
これらの変更は最終的にデータベースに保存することが前提になっていることが多いはず。
その時必要な情報は、
- 新しく追加するアイテムのリスト
- 削除するアイテムのリスト
- 変更されたアイテムのリスト
なのですが、それを管理するのは思いのほか面倒です。
例えば、
- 「A」を追加したあと「A」を削除した結果、DB上そのAはINSERTもDELETEもする必要がない。
- 「B]に変更を加えた後、「B」を削除した結果、DB上はUPDATEする必要はない、DELETEするだけ。
- 「C」に変更を加え、いろいろ変更したけっか元の内容になった。DB上UPDATEする必要がない。
- 「D」を追加したけど、やっぱり「D」を削除した。DB上、そのDをINSERTもDELETEもする必要がない。
- 「E」を削除したけど、なんだか「E]を再び追加した。DB上、DELETEもINSERTもする必要がない。
などなど。
これらの状態を追跡することで最低限の変更だけを検出して、それをDBへ更新できるようにします。
考え方
コレクションのアイテムは3つの状態を持ちます。
- ATTACHED はアイテムがトラッキングされた状態です(何も操作をしていない状態)。
- REMOVED はアイテムが削除されたものであることを意味します。。
- ADDED はこのアイテムが追加されたものであることを意味します。
ATTACHED
はすでにDBに存在するアイテムなのが条件であり、
そのコレクションがトラッキング開始時に設定されるデフォルトの状態です。
また変更の場合はCHANGED
やUPDATED
などの状態は持ってません。
トラッカーにアイテムをセットすることでアイテムはトラッキングされます。
トラッカーはトラッキングされた時点で
- アイテムのインスタンス
- トラッキング時点のアイテムのコピー
- アイテムの状態(初期値はATTACHED)
を持ちます。
変更を知りたいときは?
そのアイテムとアイテムのトラッキング時のコピーを比較することで変更を検出します。
変更を状態として持つのではなく、都度比較することで設計がシンプルになります。
注意点
理論上うまくいくはず!
と思ってますが、
ワタクシ、結構アホなので穴があるかもしれません。
そもそもまだテストしてません。
あくまでアホが考えた理論だと割り切ってください。
変更のシナリオ
実際にアイテムのトラッキングのされ方を見ていきます。
Attach
最初に「AAA, BBB, CCC, DDD, EEE」の5つのアイテムがあるコレクションをトラッキングします。
トラッキングされたので状態はすべてATTACHED
です。
Remove
次にAAA
とBBB
を削除します。
それらはREMOVED
に変更されます。
トラッキングから排除されるのではなく「削除される予定ですよ」というマークが付けられたと考えてください。
Add
次に新しくFFF
とGGG
を追加します。
これらは新規追加なので状態はADDED
になります。
AddとRemoveの逆転
ここでちょっと踏み込んだ内容です。
BBB
を追加し、FFF
を削除します
状態がREMOVED
のアイテム(BBB)を「追加」するとどうなるでしょう?
答えはATTACHED
に戻ります。
「削除して追加」はDB上なにもする必要はないことになります。
逆にADDED
のアイテム(FFF)を削除はどうなるでしょう?
なんとトラッキングがら除外されてしまいます
考え方はシンプルです。
新しくアイテムを追加して、そのアイテムを削除しました。
これは「追加したいけど、やっぱやめた」わけでDB上は最初から何もなかったことになります。
なのでトラッキングから除外されるだけです。
ちょっとわかりにくいと思いますが、
「BBB」はもともとDBに存在するアイテムなのでトラッキングされたまま状態が変化します。
「FFF」はそもそもDBに存在しないので単純にトラッキングから外されます。
Change
最後に「AAA, BBB, DDD, GGG」を変更します。
トラッキング上CHANGED
という状態は用意してありません。
なのでトラッカーとは別にCHANGED
を作り出してください。
トラッキング時のコピーと現在の値を比較して違えばCHANGED
になるように作ります。
変更はアイテムの状態がATTACHED
の時だけ検出するようにします。
この例ではもともとATTACHED
だった「BBB, DDD」だけをCHANGED
として扱います。
なぜかというと、
-
状態が
REMOVED
のアイテムを変更したところでDB上ではどうせDELETE
されるからUPDATE
する必要はないわけです。 -
状態が
ADDED
のアイテムについても、DB上ではその時点でのアイテムをINSERT
すればいいだけなので変更を検出する意味がありません。
ということです。
なので変更されたアイテムを取得するのは状態がATTACHED
であり、なおかつ変更が検出されたものに限ります。
実装はGitHubで公開してます。
ソースコード:
デモページ: