如何扩展适配React-hook-form的Mantine MultiSelect以支持数值类型?
问题描述
我正在使用React-hook-form结合Mantine MultiSelect组件与Zod进行开发,已经封装了适配React-hook-form的MultiSelect组件:
import { type UseControllerProps, useController, type FieldValues, } from "react-hook-form"; import { MultiSelect as $MultiSelect, type MultiSelectProps as $MultiSelectProps, } from "@mantine/core"; export type MultiSelectProps<T extends FieldValues> = UseControllerProps<T> & Omit<$MultiSelectProps, "value" | "defaultValue">; export function MultiSelect<T extends FieldValues>({ name, control, defaultValue, rules, shouldUnregister, onChange, ...props }: MultiSelectProps<T>) { const { field: { value, onChange: fieldOnChange, ...field }, fieldState, } = useController<T>({ name, control, defaultValue, rules, shouldUnregister, }); return ( <$MultiSelect value={value} onChange={(e) => { fieldOnChange(e); onChange?.(e); }} error={fieldState.error?.message} {...field} {...props} /> ); }
目前为了通过Zod验证,我把下拉选项的value转成了字符串:
const dropDownResourcesValues = resources.map((x: any) => ({ label: x.id.toString(), value: x.id.toString(), })); <MultiSelect style={{ width: "50%" }} required size="md" radius="md" label="Tags" withAsterisk placeholder="Please enter tags" name={"tags"} data={dropDownTagValues} control={control} />
但后端接口要求POST请求中该字段为数值类型,请问是否可以扩展MultiSelectProps以支持数值作为选项值?
解决方案
当然可以扩展封装的组件来支持数值类型的选项值,核心思路是在组件内部做字符串与数值的双向转换,既满足Mantine MultiSelect默认的字符串值要求,又能让React-hook-form的字段值保持为数值类型,同时兼容Zod验证和后端接口需求。
步骤1:扩展组件Props,添加类型转换配置
给MultiSelectProps新增一个可选参数valueType,用来指定最终提交的字段类型(string或number),同时明确泛型支持:
import { type UseControllerProps, useController, type FieldValues, } from "react-hook-form"; import { MultiSelect as $MultiSelect, type MultiSelectProps as $MultiSelectProps, } from "@mantine/core"; // 新增类型转换选项 type ValueType = "string" | "number"; export type MultiSelectProps<T extends FieldValues> = UseControllerProps<T> & Omit<$MultiSelectProps, "value" | "defaultValue"> & { valueType?: ValueType; };
步骤2:修改组件逻辑,实现双向类型转换
在组件内部处理value和onChange的转换逻辑:
- 传给Mantine组件的显示值统一转成字符串(适配组件要求)
- 用户选择后的值根据
valueType转成对应类型,同步给React-hook-form - 保持默认逻辑兼容,默认值仍为
string类型
export function MultiSelect<T extends FieldValues>({ name, control, defaultValue, rules, shouldUnregister, onChange, valueType = "string", // 默认字符串类型 ...props }: MultiSelectProps<T>) { // 预处理默认值类型 const processedDefaultValue = defaultValue ? valueType === "number" ? (defaultValue as unknown as string[]).map(Number) : defaultValue : undefined; const { field: { value, onChange: fieldOnChange, ...field }, fieldState, } = useController<T>({ name, control, defaultValue: processedDefaultValue, rules, shouldUnregister, }); // 转成Mantine组件需要的字符串值 const displayValue = valueType === "number" ? (value as unknown as number[]).map(String) : value; return ( <$MultiSelect value={displayValue} onChange={(selectedValues) => { // 转成最终提交需要的类型 const finalValues = valueType === "number" ? selectedValues.map(Number) : selectedValues; fieldOnChange(finalValues); onChange?.(finalValues); }} error={fieldState.error?.message} {...field} {...props} /> ); }
步骤3:使用组件时指定数值类型
现在可以直接使用数值类型的选项数据,无需手动转字符串,组件内部会自动处理转换:
// 直接保留数值作为选项value const dropDownResourcesValues = resources.map((x: any) => ({ label: x.id.toString(), value: x.id, // 这里用原始数值类型 })); <MultiSelect style={{ width: "50%" }} required size="md" radius="md" label="Tags" withAsterisk placeholder="Please enter tags" name={"tags"} data={dropDownResourcesValues} control={control} valueType="number" // 指定提交值为数值类型 />
步骤4:Zod验证配置
此时Zod可以直接验证数值数组,无需再针对字符串处理:
import { z } from "zod"; const formSchema = z.object({ tags: z.array(z.number()).nonempty("请选择至少一个标签"), });
这样既满足了Mantine组件的显示需求,又能让React-hook-form的字段值保持为数值类型,完美适配后端接口要求。
内容的提问来源于stack exchange,提问作者Manfre
相关产品推荐
相关产品推荐

