frontendでも外部との境界で、仕様を満たした型に変換する
やりたいこと
https://gyazo.com/141c5002b4fd3e5c576a9061e3fe2e18
frontendにおける外部との境界とはどこか?
formの入力
URL
serverとのアクセス
local storageなど(?)
どうしたいか
外部の汚いデータから、内部の綺麗なデータに変換する処理を一箇所にのみ書く
form部分の解決策
serverとのアクセス
実装を書きながら実例を集めている最中mrsekut.icon
良い感じにまとまったら、タイトルをもうちょい簡潔にしたい
zodという単語を含めることで、typescriptであることが前提出来る
ノリ
前準備
単純な例として、OrderIdのようなものを考える
entityに以下のようなものを書く
OrderIdが満たすべき条件
code: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のものから生成した型を書く
code:features/order/types.ts
import { z } from 'zod';
import { validateOrderId } from './entities';
export type OrderId = z.infer<typeof validateOrderId>;
相互依存になりうるが気にしないmrsekut.icon
zodは、どうしてもentity←typeという依存関係になってしまう
気持ちが悪いが仕方がない
URL部分の例
Next.jsのdynamic routingを使用したときに「このページのパラメータが欲しい」ということがよくある
なぜかNext.jsのそれは型がクソ雑なので、
例えば、/order/[orderId]のようなページで、orderIdを取得すると
null | string | string[]かなんかの型になる(忘れた)
そこのvalidationも自分でやれということだろう
code: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.icon
zodとnewtype-tsを組み合わす例
ここまでするほどか?