フロントエンドで扱う値の型について
typescript見よう見まねで最近は、reactでアプリを作ったりしているんだけど。どういう粒度で型を定義したら良いのか分からない。これがまた、分からなさを上手く言語化できてないので、どうやって解決していったら良いのか道が見えない。
何らかのアプリケーションを作ろうと思うと、まずはDBのデータモデルを設計する事になる。これは話はシンプルで、必要なエンティティを考えて、そいつらのidと外部キーを設定すれば良い、くらいのもの。
rest apiのサーバーサイドのプログラムは、そんなに難しくなくて、要は、渡ってきた値が想定通りかをチェックしてよければ保存すれば良い。
GraphQLを使い出すと、ここでも型についての悩みが発生する。DBの世界は、正規化されており、AというエンティティがBと関連を持ってると、Aの中にB_id
みたいな列があって、これでAとBを関連づけて終わり。何だけど、GraphQLの中では、Aの中にBのオブジェクト自体を持つことになる。しかも、このオブジェクトの実態がいるかどうかは、クエリの内容によって変わったりする。というわけで、DBに物理的に保存されている情報とプログラムの中で表現される実態には、差異がある。で、これは、自分としては、DBに保存するときの型とプログラムで扱うときの型を分離するしかないんだろうなと思っている。で、プログラムで扱うときの型は、いろんなところがnullableというかoptionalになっていて、DBへの保存とか、なんかクリティカルなイベントの前に値をバリデーションするという使い方になるんじゃないかな。あるいは、なんか細かい処理ごとに、関数に切り出してあげて、その関数は特定のフィールドが必須になっているような引数を受け取るようになっていて、その関数の呼び出しの前に、値のバリデーションを入れるとか。
フロントのプログラミングの時も同じような悩みがあり、特にしんどいのは、編集中のオブジェクトをstateとして持たないといけない。reactでは、テキストフィールドなどで何かの情報をユーザーから受け取ろうと思ったら、useStateで保存して、そのstateとフォームの値を同期させるようなコードを書くんだけど、ここで、ユーザーが入力途中の場合は、中途半端な状態のオブジェクトを扱わないといけなくなる。ということは、やっぱり、この時には全部のフィールドがoptionalな型を作っておいて、何かしらのアクションの前にバリデーションを入れるとか、アクションを起こすUIのenable/disableをオブジェクトの状態で判定するとか、そういう実装になるんだろう。
ということをまとめると、オブジェクト相互の入れ子関係も全部定義されたメモリ上の型と、DBに保存するときのテーブルのスキーマの型の2つを定義しといて、中途半端な状態の型については、適宜optionalにするような実装にすれば良いのだろうか。