「item: Item」と書いた型は、誰にも検証されていませんでした

転職用のポートフォリオとして、個人でWebアプリ「Hubpin」を作っています。
制作の経緯は「Hubpin 開発記録」に時系列でまとめています。
設計と判断は自分で行い、実装も原則自分で書いています。AIには手順書・レビュー・調査を任せています。

前回、! を「値がある保証」だと思って書いていた話を書きました。
その翌日、同じことを別の記号でやっていたことに気づきました。

自作アプリ「Hubpin」の公開ページで、Supabase から取ってきたカードを並べる部分です。

{(items ?? []).map((item: Item) => (
  <li key={item.id}>{item.title}</li>
))}

Item は自分で定義した型で、title があって url があって、という形を書いてあります。
これで中身が保証されると思っていました。

目次

照合する相手がいませんでした

Supabase のクライアントは、データベースの型を渡して初めて data が何なのかを知ります。
このときは渡していませんでした。

そこに : Item と書き足しても、TypeScript は照合しません。
形の分からない値はどんな型にも代入できてしまうため、
Item と実際の中身が食い違っていても、そこに矛盾があるとは判断されないまま通ります。

確かめる相手がいませんでした。

つまりこの注釈は、型を確かめる記述ではありません。
「これは Item として扱う」という宣言でした。

DB のカラム名を1つ変えても、この行は何も言いません。
気づくのは、画面に何も出なくなったときです。

前日に踏んだ ! と同じ形でした

並べてみたら、書いた本人の認識と、実際にやっていることのずれ方が同じでした。

書き方 書いたときの認識 実際にしていること
value! 値があると保証する 型チェックを黙らせる
(item: Item) => 中身が Item だと保証する そう扱うと宣言する

! のほうは、少なくとも「何かを黙らせている」自覚がありました。
型注釈のほうにはそれがありません。

型を書くのは、普通は安全側の作業です。
だから : Item と打った時点で手当てを1つ終えたつもりになっていて、
そのつもりが残っているぶん、こちらのほうが気づきにくかったのだと思います。

打ったのは4文字です。

入れるタイミングを次の工程の頭にしました

直し方ははっきりしています。
supabase gen types でデータベースから型を生成し、クライアントに渡す。
そうすると data が実際のテーブル定義から型付けされて、注釈が検証される側に回ります。

ただ、この日は入れませんでした。
やっていたのは公開ページを表示する工程で、型の整備はその範囲に入っていません。
途中で足すと、表示を確かめている最中に型の整備が挟まることになり、
うまくいかなかったときにどちらが原因なのか分からなくなります。

代わりに、次のCRUDを書く工程の頭で入れる、と決めて送りました。
型を使う場所は画面が増えるたびに増えていくので、置き換えを後ろに倒すほど直す箇所が広がり、
そのぶん動作確認のやり直しも増えます。

早いほうが安い、というだけの理由です。

おわりに

この工程で分かったのは、TypeScript が守ってくれる範囲が思っていたより狭いことでした。
そして守られていない場所には、たいてい自分で書いた記号が立っています。

!: Item も、書けば赤線は消えます。
そのときは消えたことだけを確認して次へ進んでいたので、
なぜ消えたのかは見ていませんでした。

見ていれば、どちらも同じ理由でした。

次回は、実際に型を生成して置き換えたときの話です。
守られる側に回るはずだったのですが、1箇所だけ、前より弱くなりました。

目次