在不使用useEffectEvent的情况下避免自定义钩子中的无限循环?

问题描述 投票:0回答:1

这是我正在开发的一个简单表单库的摘录。表单具有初始值和当前值。当前值可以重置为初始值。可选,重置后也可以更改初始值:

const useForm = ((initialInitialValue: T) => {
  // Uses state since the initial value can be changed by 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};
};

不幸的是,使用

reset
函数很容易创建无限循环:

// Wait until myInitialValue is available (e.g. from the react-query's useQuery):
useEffect(() => {
  if (myInitialValue === undefined) {
    return;
  }
  reset(myInitialValue);
}, [myInitialValue, reset]);

由于

reset
改变了钩子的
initialValue
reset
将成为一个新函数,它将再次触发该效果,无限循环。

人们可以想象一些 hacky 解决方法,例如将

useRef
useEffect
一起使用,仅调用
reset
一次(如果这在您的应用程序中合适),但这非常脆弱,因为
useForm
自定义挂钩的每个用户都必须这样做(并且必须意识到他们需要这样做)。换句话说,上面写的
useForm
是一个糟糕的、有漏洞的抽象。

在 React 的未来版本中,我们可以使用

useEffectEvent
来实现上述
useRef
hack 的稍微好一点的版本。然而,

  • useEffectEvent
    今天不可用并且
  • 它仍然让调用者承担责任,并且仍然可以轻松编写无限循环。

建议怎样写

useForm
才能不具有此属性。

reactjs react-hooks
1个回答
0
投票

利用useRef来存储初始值和重置逻辑。这可以防止依赖项导致不必要的重新渲染。

const resetRef = useRef();

// Store the reset function in the ref to prevent re-creation
  useEffect(() => {
    resetRef.current = reset;
  }, [reset]);

更改退货声明

 return { reset: resetRef.current, setValue, value };

  1. resetRef用于存储重置函数,以防止每次initialValue改变时都重新创建它。这可以避免 useEffect 中的无限循环。
  2. 重置函数仍然使用useCallback创建以保持记忆,但组件中实际使用的引用是稳定的,防止因函数更改而导致重新渲染。
  3. 每当重置函数发生变化时,都会使用效果来更新resetRef.current。这确保了组件中使用的引用始终是最新的,但不会触发额外的渲染。
  4. 在组件中我们从 myForm 调用重置函数
const MyFormComponent = ({ myInitialValue }) => {
 const { reset, setValue, value } = useForm(myInitialValue);
useEffect(() => {
 useEffect(() => {
 if (myInitialValue === undefined) {
   if (myInitialValue !== undefined) {
   return;
 }
 reset(myInitialValue);
     reset(myInitialValue);
   }
}, [myInitialValue, reset]);
 }, [myInitialValue, reset]);
 return (
   <div>
     <input value={value} onChange={(e) => setValue(e.target.value)} />
     <button onClick={() => reset()}>Reset</button>
   </div>
 );
};
© www.soinside.com 2019 - 2024. All rights reserved.