generated at
frontendでも外部との境界で、仕様を満たした型に変換する

やりたいこと
前提としてvalidationのロジックをEntityに書くようにしておく


frontendにおける外部との境界とはどこか?
formの入力
URL
serverとのアクセス
local storageなど(?)


どうしたいか
外部の汚いデータから、内部の綺麗なデータに変換する処理を一箇所にのみ書く
それの返り値が、branded typesになっている
内部で使用する関数は全てbranded typesを引数に取れば良い



form部分の解決策
jotai-decode-formを作ったmrsekut


serverとのアクセス
tRPCを使う





実装を書きながら実例を集めている最中mrsekut
良い感じにまとまったら、タイトルをもうちょい簡潔にしたい
zodという単語を含めることで、typescriptであることが前提出来る





ノリ
この際に、zodに頼る



前準備
単純な例として、 OrderId のようなものを考える
entityに以下のようなものを書く
OrderId が満たすべき条件
zod.brand()を使った定義
features/order/entities.ts
import { OrderId } from './types'; import { z } from 'zod'; import { str2num } from 'app/src/utils/functions'; export const mkOrderId = (id: string | number): OrderId => { const numId = typeof id === 'string' ? str2num(id) : id; return validateOrderId.parse(numId); }; /** @package */ export const validateOrderId = z.number().brand<'OrderId'>();
↑は内容が薄いが、ここに「 OrderId が満たすべき条件」を全て列挙する
「OrderIdの仕様は何だっけ?」となったときにこのファイルを見れば完全に理解できる状態を目指す
typesには、entityのものから生成した型を書く
features/order/types.ts
import { z } from 'zod'; import { validateOrderId } from './entities'; export type OrderId = z.infer<typeof validateOrderId>;
相互依存になりうるが気にしないmrsekut
zodは、どうしてもentity←typeという依存関係になってしまう
気持ちが悪いが仕方がない


URL部分の例
Next.jsのdynamic routingを使用したときに「このページのパラメータが欲しい」ということがよくある
なぜかNext.jsのそれは型がクソ雑なので、
例えば、 /order/[orderId] のようなページで、 orderId を取得すると
null | string | string[] かなんかの型になる(忘れた)
そこのvalidationも自分でやれということだろう
ts
import { OrderId } from './types'; import { usePathname } from 'next/navigation'; import { mkOrderId } from './entities'; export const useCurrentOrderId = (): { orderId: OrderId } => { const path = usePathname(); const orderId = path?.split('/')[2]; if (orderId == null) { throw new Error('Order ID is not found in the URL'); } return { orderId: mkOrderId(orderId) }; };
hooksの中でthrow Errorってしていいんだっけ #??
「URL」という外部から、値を中に持ち込むときにしっかりvalidationして、仕様を満たした型に変換する





ついでにこの辺を整理したいmrsekut




zodとnewtype-tsを組み合わす例
ここまでするほどか?