我正在使用 RHF 并尝试创建一个自定义表单组件,该组件采用一组字段,然后为每个字段呈现输入。 例如,它可能需要这样的输入:
const fields = [
{
type: 'select',
name: 'data',
label: 'Location/Date',
options: [
{ name: 'a', color: 'blue' },
{ name: 'b', color: 'green' },
{ name: 'c', color: 'purple' },
],
getDisplayText: (item) =>
,
},
{
type: 'text',
name: 'harvestSchemaId',
label: 'Harvest Schema ID',
validation: {
required: 'Harvest Schema ID is required',
},
},
{
type: 'text',
name: 'pinId',
label: 'Pin ID',
validation: {
required: 'Pin ID is required',
},
},
]
然后组件映射所有输入并根据 field.type 渲染 或 。 我遇到的问题是我的选择类型采用通用选项:
interface BaseField {
name: string
label?: string
validation?: RegisterOptions
}
interface InputField extends BaseField {
type: 'text' | 'email' | 'password'
}
interface SelectField<T> extends BaseField {
type: 'select'
options: T[]
getDisplayText?: (item: T) => string
}
因为 getDisplayText 回调需要与 options 具有相同的类型。 我的问题是,我到底怎样才能输入所有内容以确保类型安全?我尝试过像这样输入字段:
const fields: Field<{ name: string; color: string }>[] = ...
但这仅在有一个选择时才有效。如果有多个,我必须建立一个联盟:
const fields: Field<{ name: string; color: string } | { test: string }>[] = ...
然后我失去了 getDisplayText 回调的所有类型安全性。 另外,我知道我可以这样做:
const fields: [
SelectField<{ name: string; color: string }>,
InputField,
InputField,
] = ...
但我觉得必须有更好的方法,并且我正在努力使其尽可能可重用且非样板化。 有什么好的方法可以做到这一点?我知道这一定是一个非常常见的用例,但我在网上找不到任何东西。 感谢您的帮助!
我解决这个问题的一种方法是定义一个无操作辅助函数来进行类型检查。
function createField(field: InputField): BaseField;
function createField<T>(field: SelectField<T>): BaseField;
function createField(field: BaseField) {
return field;
}
createField
是一个对每个 BaseField
类型具有多个重载的函数,它只会返回类型为 BaseField
的相同对象。它将强制打字稿对其输入进行类型检查,使其实际上是 InputField
或 SelectField<T>
。
然后你可以像这样使用它:
const fields: BaseField[] = [
createField({
type: 'select',
name: 'data',
label: 'Location/Date',
options: [
{ name: 'a', color: 'blue' },
{ name: 'b', color: 'green' },
{ name: 'c', color: 'purple' },
],
getDisplayText: (item) => item.name
}),
createField({
type: 'text',
name: 'harvestSchemaId',
label: 'Harvest Schema ID',
validation: {
required: 'Harvest Schema ID is required',
},
}),
createField({
type: 'text',
name: 'pinId',
label: 'Pin ID',
validation: {
required: 'Pin ID is required',
},
}),
];