介面資料型別與表單提交資料型別,在大多數情況下,大部分屬性的型別是相同的,但很少能做到完全統一。
我在之前的工作中經常為了方便,直接將介面資料型別複用為表單內資料型別,在遇到屬性型別不一致的情況時會使用any
強制忽略型別錯誤。
後來經過自省與思考,這種工作模式會引起各種隱藏bug,一定有更好的工程解決方案。
我的答案就是:為表單提交資料單獨定義型別!
請求資料型別
type RequestBody = {
name?: string
count?: number
groupIds?: number[]
startDate?: string // YYYY-MM-DD
}
type FormValue = {
name?: string
count?: number
groupIds?: string
startDate?: Moment
}
有了該型別,我們可以方便的將該型別使用在表單範例上
const [form] = Form.useForm< FormValue >()
如果表單的資料型別和介面提交的資料型別完全一致,當然可以共用一個,但現實世界這種情況幾乎沒有。
大多數情況是可以複用一些介面的屬性到表單的資料型別中,例如上面的兩個資料結構,其中 name、id 屬性是相同的,則FormValue 可以優化為
type FormValue = Pick< RequestBody, 'name' | 'count' > {
groupIds?: string
startDate?: Moment
}
應用此時表單元件的name
約束就應為我們自定義的表單資料型別FormValue
,定義約束元件
const FormItem = Form.Item as React.FC<
Omit< FormItemProps, 'name' > & {
name: keyof FormValue
}
>
應用該約束元件
< FormItem label="名稱" name="name" > ...
在表單的onFinish
提交過程中,需要一個將FormValue(表單資料)轉換為RequestBody(提交資料)的函數,型別定義如下:
const formValueToRquestBody = (values: FormValue): RequestBody => {
return {
name: values.name,
id: values.id,
groupIds: values.groupIds.split(',').map(n => Number(n)),
startDate: values.startDate?.format('YYYY-MM-DD'),
}
}
複雜表單有些表單資料並非一層的key => value
,而是多層樹狀或陣列結構。
例如:提交資料結構
type FormValue = {
name: string
rule: {
min: number
max: number
}
}
表單中關於rule 的寫法為:
< Form.Item name={['rule', 'min']}>
這種情況下,name
不再是簡單的字串,應該如何用型別約束?
如果可以我希望使用型別工具,相容多層表單資料結構,但一直沒成功。
我目前的方法是,為該表單層級安排一個專用型別,該方法會有些寫的麻煩,但勝在能準確的定義好型別。
我在採用該方法校驗表單name資料時發現了幾個很難發現的拼寫錯誤,提前制止了測試同學提bug過來。
例如為rule
屬性定義單獨的RuleFormItem
:
import type { FormItemProps } from 'antd'
const RuleFormItem = Form.Item as React.FC<
Omit< FormItemProps, 'name'> & {
name: ['rule', keyof FormValue['rule']]
}
>
呼叫時
< RuleFormItem label="min" name={['rule', 'min']}> ...
此時陣列中的 rule 與 min 都能收到型別的保護。
export type TypedFormItem< T > = React.FC<
Omit< FormItemProps, 'name' > & {
name: T
}
>
const RuleFormItem = Form.Item as TypedFormItem< keyof FormValue >