如何让自定义Hook的状态更新函数兼容useState用法并自动处理数据?
兼容原生调用的useForm Hook状态更新实现
核心方案
要同时支持直接传值和函数式更新,并且在开启autoUpdate时自动为字段添加计算属性,核心是先统一获取原始更新状态,再对状态进行自动处理,最后调用原生useState的setter完成更新。
完整代码实现
import { useState, useCallback } from 'react'; // 定义输入字段类型:基础字段结构 + 自动计算的res属性 type InputField = { value: string | number; res?: number; }; // 表单状态类型:键为字段名,值为InputField type InputFieldsState = Record<string, InputField>; // 支持两种更新方式:直接传状态对象,或接收prevState的更新函数 type SetInputFieldsAction = InputFieldsState | ((prevState: InputFieldsState) => InputFieldsState); export function useForm(initialState: InputFieldsState, autoUpdate = true) { const [inputFields, setOriginalState] = useState<InputFieldsState>(initialState); const setInputFields = useCallback((action: SetInputFieldsAction) => { // 第一步:获取用户更新后的原始状态 const rawUpdatedState = typeof action === 'function' ? (action as (prev: InputFieldsState) => InputFieldsState)(inputFields) : action; // 第二步:如果开启autoUpdate,为每个字段添加计算属性res const finalState = autoUpdate ? Object.fromEntries( Object.entries(rawUpdatedState).map(([fieldName, field]) => [ fieldName, { ...field, // 示例计算逻辑:字符串取长度,数字乘2,可根据需求修改 res: typeof field.value === 'string' ? field.value.length : field.value * 2 } ]) ) : rawUpdatedState; // 第三步:调用原生setter更新状态 setOriginalState(finalState); }, [inputFields, autoUpdate]); return { inputFields, setInputFields }; }
关键细节说明
- 类型兼容:通过
SetInputFieldsAction联合类型明确两种更新参数的类型,解决函数式更新时的类型错误。 - 更新逻辑统一:不管用户用哪种方式更新,先拿到原始更新结果,再统一处理计算属性,保证
autoUpdate逻辑在所有场景下生效。 - 性能优化:用
useCallback缓存setInputFields,避免组件每次重渲染都创建新函数,减少不必要的子组件渲染。
使用示例
import { useForm } from './useForm'; function DemoForm() { const { inputFields, setInputFields } = useForm( { username: { value: '' }, score: { value: 0 } }, true // 开启自动计算 ); // 直接传值更新 const handleUsernameChange = (e: React.ChangeEvent<HTMLInputElement>) => { setInputFields({ ...inputFields, username: { value: e.target.value } }); }; // 函数式更新(依赖prevState) const handleAddScore = () => { setInputFields(prev => ({ ...prev, score: { value: prev.score.value + 10 } })); }; return ( <div> <input type="text" value={inputFields.username.value} onChange={handleUsernameChange} placeholder="输入用户名" /> <p>用户名长度:{inputFields.username.res}</p> <button onClick={handleAddScore}>加分</button> <p>双倍分数:{inputFields.score.res}</p> </div> ); }
内容的提问来源于stack exchange,提问作者SlothOverlord
相关产品推荐
相关产品推荐

