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

不使用useEffectEvent,如何避免useForm自定义Hook中的无限循环?

避免useForm重置引发无限循环的推荐实现方式

问题场景

我正在开发一个简易表单Hook,需求是表单维护初始值与当前值,支持将当前值重置为初始值,同时允许重置时修改初始值。最初的实现如下:

const useForm = <T>(initialInitialValue: T) => {
  // 用state存储初始值,因为reset可以修改它
  const [initialValue, setInitialValue] = useState(initialInitialValue);
  const [value, setValue] = useState(initialInitialValue);

  const reset = useCallback((newInitialValue?: T) => {
    if (newInitialValue !== undefined) {
      setInitialValue(newInitialValue);
    }
    setValue(newInitialValue ?? initialValue);
  }, [initialValue]);

  return { reset, setValue, value };
};

但调用这个Hook时很容易触发无限循环,比如在useEffect中等待异步数据(如react-query返回的结果)后调用reset:

// 等待myInitialValue就绪(比如从react-query的useQuery获取)
useEffect(() => {
  if (myInitialValue === undefined) {
    return;
  }
  reset(myInitialValue);
}, [myInitialValue, reset]);

问题根源

问题出在reset函数的依赖上:reset依赖initialValue状态,当调用reset(newInitialValue)时,会修改initialValue,导致reset被重新创建。而useEffect的依赖数组包含reset,因此reset更新后会再次触发useEffect,进而再次调用reset,形成无限循环。

虽然可以用useRef配合useEffect做单次调用的取巧处理,但这种方案需要每个使用者都知晓并手动实现,属于抽象泄漏,非常脆弱。未来的useEffectEvent虽然能优化这类场景,但目前尚未稳定可用,且仍将避免循环的责任推给了调用方。

推荐实现方案

方案一:用useRef持久化初始值,让reset保持稳定

核心思路是用useRef存储初始值,而非useState,这样reset函数不需要依赖任何状态,始终保持同一个引用,从根源上避免依赖变化导致的循环。

const useForm = <T>(initialInitialValue: T) => {
  // 用useRef持久化初始值,不受重新渲染影响
  const initialValueRef = useRef(initialInitialValue);
  const [value, setValue] = useState(initialInitialValue);

  const reset = useCallback((newInitialValue?: T) => {
    if (newInitialValue !== undefined) {
      // 更新ref中的初始值
      initialValueRef.current = newInitialValue;
    }
    // 重置当前值,优先用新传入的初始值,否则用ref中保存的初始值
    setValue(newInitialValue ?? initialValueRef.current);
  }, []); // 无依赖,reset始终是同一个函数

  return { reset, setValue, value };
};

方案二:保留initialValue状态,用函数式更新避免依赖

如果必须用useState存储初始值,可以通过函数式更新获取最新的初始值,同时移除reset对initialValue的依赖:

const useForm = <T>(initialInitialValue: T) => {
  const [initialValue, setInitialValue] = useState(initialInitialValue);
  const [value, setValue] = useState(initialInitialValue);

  const reset = useCallback((newInitialValue?: T) => {
    if (newInitialValue !== undefined) {
      // 同时更新初始值和当前值
      setInitialValue(newInitialValue);
      setValue(newInitialValue);
    } else {
      // 用函数式更新获取最新的initialValue,避免依赖
      setValue(() => initialValue);
    }
  }, []); // 依赖为空,reset保持稳定

  return { reset, setValue, value };
};

效果验证

优化后的useForm中,reset函数始终是同一个引用,调用方无需额外处理,直接在useEffect中使用即可避免无限循环:

useEffect(() => {
  if (myInitialValue === undefined) return;
  reset(myInitialValue);
}, [myInitialValue, reset]);

此时只有myInitialValue变化时才会触发useEffect,reset的调用不会导致自身重新创建,彻底解决循环问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 18:06:01