TypeScript な React のフォームを作成していて new FormData()
で取得したフォーム内容のデータの型を送信する形式の interface から作成したかったのメモ
FormData で取得されるデータの形式
const formData = [...new Form(form).entries()]; // => [[name1, value1], [name2, value2], ...]
new Form(form)
は FormData オブジェクトなので、フォームの name
と value
を扱うために FormData.entries()
したものを使う。
FormData.entries()
はフォームの [name, value]
のセットの配列を返す。
interface から FormData.entries()
の返す配列の型を作成する
interface
interface IFromSchema { name: string; email: string; amount: number; isAgree: boolean; }
FormData.entries()
の返す形式が [name, value][]
なので、name
は IFromSchema
の key、value
の型は IFromSchema[key]
で最終的に次のような型ができれば良さそう
type IFromSchemaEntries = [ ['name', string]; ['email', string]; ['amount', number]; ['isAgree', boolean]; ];
タプルの方が良いのかな?
Mapped Types を利用して Entries で作成される型を作る
type Entries<T> = { [K in keyof T]: [K, T[K]]; }[keyof T][];
👇
type T1 = { [K in keyof IFromSchema]: [K, IFromSchema[K]] }; // T1 は次のようなオブジェクト形式の型になる // { // name: [name, string], // email: [email, string], // amount: [amount, number], // isAgree: [isAgree, boolean], // } type T2 = T1[keyof IFromSchema]; // T2 はオブジェクトに[key] でアクセスしている形なので次用な型になる // => type T2 = [name, string] | [email, string] | [amount, number] | [isAgree, boolean]; // 配列 or タプル形式になっていれば良いので type T3 = T1[keyof IFromSchema][]; // => type T3 = ([name, string] | [email, string] | [amount, number] | [isAgree, boolean])[]; // key と key に対応した型がセットになったタプル型の配列になる // 抽象化して type Entries<T> = { [K in keyof T]: [K, T[K]]; }[keyof T][]; type FormEntriesType = Entries<IFromSchema>; // => ([name, string] | [email, string] | [amount, number] | [isAgree, string])[];
これで Object.entries()
したときの型が Object の interface から作れつようになりました!
但し、([name, string] | [email, string] | [amount, number] | [isAgree, string])
なので確実に全ての key が含まれているとは限らない配列ということになると思います。Form のチェックボックスとかチェックがない場合 FromData に含まれないからこの方が扱いやすいかも!?
FormData で使ってみる
const handleFormSubmit = (evt: React.FormEvent<HTMLFormElement>) => { evt.preventDefault(); const form = formRef.current; if (!form) { return; } const FormDataEntries = [...new Form(form).entries()] as Entries<IFromSchema>; // Entries<IFromSchema> はキーが不足していても通るのでバリデーションエラーになるデフォルトのデータを用意しておく const defaultData: IFromSchema = { name: '', email: '', amount: 0, isAgree: false, }; // IFromSchema 形式に戻してvalidation / post する const postData = FormDataEntries.reduce((data, item) => { const [key, value] = item; return { ...data, [key]: value, }; }, defaultData); if (validate(postData)) { // フォームを送信 } };
[...new Form(form).entries()] as Entries<IFromSchema>
と as
で型を付けているので完全に安全な訳ではないのでバリデーションでチェックする必要があるとお思いますが、型補完が効いて扱いやすくなりました!
所管
本当にこの方法が良いのか解ってない部分がありますが、実現したいことはまぁできたかな〜という感じ。こうやって調べている中で TypeScript のジェネリックもチョットづつ分かってきた気がします!
₍ ᐢ. ̫ .ᐢ ₎👌 TIL (Today I Learned) 少しづつ強くなる
[参考]
めばちさんの絵ホント好き (ネタ画像ネタ切れ!)