介面數據類型與表單提交數據類型,在大多數情況下,大部分屬性的類型是相同的,但很少能做到完全統一。我在之前的工作中經常為了方便,直接將介面數據類型復用為表單內數據類型,在遇到屬性類型不一致的情況時會使用any強制忽略類型錯誤。後來經過自省與思考,這種工作模式會引起各種隱藏bug,一定有更好的工程解決方... ...
概述
介面數據類型與表單提交數據類型,在大多數情況下,大部分屬性的類型是相同的,但很少能做到完全統一。
我在之前的工作中經常為了方便,直接將介面數據類型復用為表單內數據類型,在遇到屬性類型不一致的情況時會使用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
}
Form.Item 限定 name 優化
應用此時表單組件的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 >