如何在React useState Hook中设置泛型类型参数?表单组件报错处理
泛型表单组件的类型错误修复
问题背景
构建接受泛型类型参数回调的表单组件时,出现三个类型错误:
- 在
onChange中展开confirmationOptions时:Type 'T' must have a 'Symbol.iterator' method that returns an iterator - 在
checked属性中:Property 'isFoo' does not exist on type 'T' - 不想通过
{} as T强制转换useState的初始值
调用代码(FormPage.tsx)
type MyConfirmationOptions = { isFoo?: boolean; }; <MyForm<MyConfirmationOptions> onConfirm={({ isFoo }) => { api.postData(isFoo) }} />
组件代码(MyForm.tsx)
import { useState, FormEvent, PropsWithChildren } from 'react'; interface MyFormProps<T> { onConfirm: (confirmationOptions: T) => void; } export function MyForm<T>( props: PropsWithChildren<MyFormProps<T>>, ){ const [confirmationOptions, setConfirmationOptions] = useState<T>({} as T); const onSubmit = (event: FormEvent) => { event.preventDefault(); props.onConfirm(confirmationOptions); }; return ( <form onSubmit={onSubmit}> <Checkbox onChange={async () => { setConfirmationOptions(confirmationOptions => ({ ...confirmationOptions, isFoo: !confirmationOptions.isFoo })) }} checked={confirmationOptions.isFoo} /> <button type="submit">确认</button> </form> ) }
错误修复方案
1. 修复对象展开的类型错误
原因:泛型T未被约束为对象类型,TypeScript无法确认它支持展开语法。
解决:给泛型添加对象类型约束:
// 更新Props和组件的泛型约束 interface MyFormProps<T extends Record<string, unknown>> { onConfirm: (confirmationOptions: T) => void; } export function MyForm<T extends Record<string, unknown>>( props: PropsWithChildren<MyFormProps<T>>, ) { // 其余代码保持不变 }
2. 修复isFoo属性不存在的错误
原因:泛型T是任意对象类型,TypeScript无法保证它包含isFoo属性。根据组件使用场景,有两种修复方式:
方式一:约束泛型必须包含isFoo
如果组件专门处理含isFoo的表单选项,直接在泛型约束中指定:
interface MyFormProps<T extends { isFoo?: boolean }> { onConfirm: (confirmationOptions: T) => void; } export function MyForm<T extends { isFoo?: boolean }>( props: PropsWithChildren<MyFormProps<T>>, ) { // 其余代码保持不变 }
方式二:实现完全通用的泛型表单
如果想让组件适配更多字段,不硬编码isFoo,可以通过传入字段配置动态生成表单:
// 更新Props,添加字段配置 interface MyFormProps<T> { onConfirm: (confirmationOptions: T) => void; fields: Array<{ name: keyof T; type: 'checkbox'; label: string; }>; initialValues: T; } export function MyForm<T extends Record<string, unknown>>( props: PropsWithChildren<MyFormProps<T>>, ) { const [confirmationOptions, setConfirmationOptions] = useState<T>(props.initialValues); const handleCheckboxChange = (name: keyof T) => { setConfirmationOptions(prev => ({ ...prev, [name]: !prev[name] })); }; return ( <form onSubmit={(e) => { e.preventDefault(); props.onConfirm(confirmationOptions); }}> {props.fields.map(field => ( <div key={String(field.name)}> <label>{field.label}</label> <Checkbox onChange={() => handleCheckboxChange(field.name)} checked={!!confirmationOptions[field.name]} /> </div> ))} <button type="submit">确认</button> </form> ); } // 调用时传入字段配置和初始值 <MyForm<MyConfirmationOptions> onConfirm={({ isFoo }) => api.postData(isFoo)} initialValues={{ isFoo: false }} fields={[{ name: 'isFoo', type: 'checkbox', label: '是否开启Foo' }]} />
3. 移除useState的类型强制转换
原因:{}无法直接赋值给泛型T,因为T可能包含必填属性。
解决:有两种可行方式:
方式一:要求传入初始值
修改组件Props添加initialValues,从外部获取合法的初始状态:
interface MyFormProps<T extends Record<string, unknown>> { onConfirm: (confirmationOptions: T) => void; initialValues: T; // 添加初始值参数 } export function MyForm<T extends Record<string, unknown>>( props: PropsWithChildren<MyFormProps<T>>, ) { // 直接使用传入的初始值,无需强制转换 const [confirmationOptions, setConfirmationOptions] = useState<T>(props.initialValues); // 其余代码保持不变 } // 调用时传入初始值 <MyForm<MyConfirmationOptions> onConfirm={({ isFoo }) => api.postData(isFoo)} initialValues={{ isFoo: false }} />
方式二:约束泛型为全可选对象
如果组件不需要处理必填属性,可将泛型约束为Partial类型:
interface MyFormProps<T extends Partial<Record<string, unknown>>> { onConfirm: (confirmationOptions: T) => void; } export function MyForm<T extends Partial<Record<string, unknown>>>( props: PropsWithChildren<MyFormProps<T>>, ) { // 此时{}是合法的初始值,无需强制转换 const [confirmationOptions, setConfirmationOptions] = useState<T>({}); // 其余代码保持不变 }
内容的提问来源于stack exchange,提问作者margeTron
相关产品推荐
相关产品推荐

