You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React动态表单中useState的previousState类型如何正确定义?

动态表单中如何正确定义setForm的prev参数类型

我开发了一个Page页面,里面包含自定义组件FormModal,这个组件会根据传入的props渲染对应的表单字段。在Page组件里,我通过prepareForm函数把formArr转换成初始表单状态,再把formArr和setForm作为props传给FormModal。现在在FormModal的handleChange方法里,调用setForm时的prev参数类型无法确定,出现了类型错误,想请教在这种动态表单逻辑下,怎么正确定义这个参数的类型。


Page组件代码

const prepareForm = (formArr: TFormField[]) => {
    return formArr.reduce((r, v) => ({ ...r, [v.name]: '' }), {})
}

const formArr: TFormField[] = [
    {
        label: 'Day name',
        name: 'name',
        type: 'text',
    },
    {
        label: 'Day date',
        name: 'date',
        type: 'date',
    },
]


const AddDayModal = (props: TModalProps) => {
    const [form, setForm] = useState(prepareForm(formArr))

    /* 其他逻辑 */

    const formModalProps: TFormModalProps = {
        formArr,
        setForm,
    }

    return (
        <div>
            <FormModal {...formModalProps} />
        </div>
    )
}

FormModal组件代码

type TFormField = {
    label: string
    name: string
    type: string
}

type TFormModalProps = {
    formArr: TFormField[]
    setForm: (prev) => void
}

const FormModal = (props: TFormModalProps) => {
    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value, type } = e.target

        props.setForm((prev) => {
            if (type == 'date') {
                return { ...prev, [name]: new Date(value) }
            } else {
                return { ...prev, [name]: value }
            }
        })
    }

     return ( /* JSX 渲染内容 */ )
}

解决方案

核心思路是让TypeScript能精准推导出表单状态的类型,而不是用模糊的any或空对象类型。可以通过以下步骤实现:

1. 给TFormField和prepareForm添加泛型约束

先限定表单字段的类型范围,再让prepareForm根据输入的字段数组推导出表单对象的具体类型:

// 限定type为可选的枚举值,避免任意字符串
type TFormField<Name extends string = string> = {
    label: string
    name: Name
    type: 'text' | 'date'
}

// 利用泛型推导表单对象的键值类型
const prepareForm = <T extends TFormField>(formArr: T[]) => {
    return formArr.reduce((r, v) => ({ ...r, [v.name]: '' }), {} as { [K in T['name']]: string | Date })
}

2. 修正TFormModalProps的类型

让setForm的类型和ReactuseState返回的setter类型完全对齐,用泛型关联表单类型:

type TFormModalProps<FormType> = {
    formArr: TFormField[]
    setForm: React.Dispatch<React.SetStateAction<FormType>>
}

3. 将FormModal改为泛型组件

让组件接收表单类型参数,这样setForm的prev参数类型会自动匹配:

const FormModal = <FormType extends Record<string, string | Date>>(props: TFormModalProps<FormType>) => {
    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const { name, value, type } = e.target

        props.setForm((prev) => {
            // prev的类型现在是明确的FormType,不会再报错
            if (type === 'date') {
                return { ...prev, [name]: new Date(value) } as FormType
            } else {
                return { ...prev, [name]: value } as FormType
            }
        })
    }

     return ( /* JSX 渲染内容 */ )
}

4. Page组件中自动推导类型

在使用FormModal时,TypeScript会自动从form的类型推导泛型参数,无需手动指定:

const AddDayModal = (props: TModalProps) => {
    // form的类型会被自动推导为 { name: string; date: string | Date }
    const [form, setForm] = useState(prepareForm(formArr))

    const formModalProps = {
        formArr,
        setForm,
    }

    return (
        <div>
            <FormModal {...formModalProps} />
        </div>
    )
}

额外优化:精准匹配字段类型

如果想让不同字段的类型更精准(比如name固定为string,date固定为Date),可以用更细致的泛型推导:

// 根据字段type映射对应的值类型
type FormFieldToType<T extends TFormField> = T['type'] extends 'date' ? Date : string

// 从字段数组生成精准的表单类型
type FormFromFields<T extends TFormField[]> = {
    [K in T[number]['name']]: FormFieldToType<Extract<T[number], { name: K }>>
}

const prepareForm = <T extends TFormField[]>(formArr: T) => {
    return formArr.reduce((r, v) => ({ 
        ...r, 
        [v.name]: v.type === 'date' ? new Date() : '' 
    }), {} as FormFromFields<T>)
}

这样form的类型会精确到{ name: string; date: Date },后续赋值操作的类型检查会更严格。


内容的提问来源于stack exchange,提问作者FlameDra

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 22:45:34