React中useCallback依赖缺失与无限循环问题的解决方案咨询(结合Formik表单场景)
这个问题的核心原因其实很好理解:formDef是useFormik每次组件渲染时返回的全新对象——哪怕它里面的方法(比如setFieldValue)逻辑没变,对象引用每次都会更新。所以把formDef放进useCallback的依赖数组时,useCallback会每次都重新创建新的函数实例,触发useEffect执行;而useEffect里调用setFieldValue又会触发组件重新渲染,形成无限循环。
下面给你几种能同时解决依赖报错和无限循环的方案,按推荐程度排序:
方案一:利用Formik的enableReinitialize自动填充(最推荐)
既然你已经开启了enableReinitialize: true,完全可以利用Formik的这个特性自动初始化表单,不需要手动调用setFieldValue。只需要把type.type的值合并到初始值里即可:
// 合并基础初始值和type带来的动态值 const mergedInitialValues = { ...formTypedValues, ...(type?.type || {}) // 当type存在时,用type.type覆盖对应字段 }; const formDef = useFormik({ initialValues: mergedInitialValues, validateOnMount: true, validationSchema, enableReinitialize: true, validateOnChange: true, onSubmit: (values) => handleMappingType(values), });
这样当type变化时,mergedInitialValues会自动更新,enableReinitialize会让Formik重新初始化表单,自动把type.type的值填充到对应字段里。全程不需要useEffect和useCallback,代码更简洁,也完全符合Formik的设计思路。
方案二:移除useCallback,直接在useEffect中处理逻辑
如果不想修改Formik的初始化逻辑,可以把InitialiseTypeStorage的逻辑直接放进useEffect,并且只依赖type和formDef.setFieldValue——注意,setFieldValue是Formik内部缓存的稳定函数,不会每次渲染都变化,所以不会触发无限循环:
useEffect(() => { if (type) { for (const [key, value] of Object.entries(type.type)) { formDef.setFieldValue(key, value); } } }, [type, formDef.setFieldValue]); // 只依赖稳定的setFieldValue,而非整个formDef
这种方案去掉了多余的useCallback,直接把逻辑内联到useEffect,既满足了React的依赖检查要求,又避免了无限循环。
方案三:用useRef缓存setFieldValue(保留useCallback的场景)
如果因为业务需求必须保留useCallback,可以用useRef来缓存setFieldValue方法,让useCallback的依赖保持稳定:
// 用ref缓存setFieldValue,确保引用不变 const setFieldValueRef = useRef(); // 每次formDef.setFieldValue更新时,同步到ref useEffect(() => { setFieldValueRef.current = formDef.setFieldValue; }, [formDef.setFieldValue]); const InitialiseTypeStorage = useCallback(() => { if (type) { for (const [key, value] of Object.entries(type.type)) { setFieldValueRef.current(key, value); } } }, [type]); // 只依赖type,不再依赖formDef useEffect(() => { InitialiseTypeStorage(); }, [InitialiseTypeStorage]);
这里useCallback只依赖type,setFieldValue通过ref间接调用,避免了因formDef引用变化导致的无限循环,同时也不会触发React的依赖报错。
内容的提问来源于stack exchange,提问作者Greg-A

