React hooks:当状态本身是依赖项时,如何更新 useEffect 中的状态?

React hooks : how can I update a state within useEffect when the state itself is the dependency?

我知道已经有一些相关的问题,比如 ,但我还是没有完全理解。

假设我根据道具设置了一个 index 状态;我需要在设置该值的任何时候对其进行清理。

<MyComponent index={4}/>

我是这样尝试的:

useEffect(() => {
  setIndex(props.index);
}, [props.index]);

useEffect(() => {
  const sanitized = sanitizeIndex(index);
  setIndex(sanitized);
},[index])

const sanitizeIndex = index => {
    //check that index exists in array...
    //use fallback if not...
    //etc.
    return index
}

它不起作用(无限循环),因为状态被监视 由第二个 useEffect().

更新

当然,我可以通过在 prop 上调用 sanitizeIndex() 来避免这种情况,所以我只需要 useEffect():

的一个实例
useEffect(() => {
  setIndex(sanitizeIndex(props.index));
}, [props.index]);

事实是,我在代码中多次调用 setIndex,我害怕错过使用 sanitizeIndex

是否有另一种方法可以“捕获”并更新正在设置的状态值?

谢谢!

所以将 useEffect 想像成 javascript 中的事件侦听器。这不是一回事,但可以这样想。事件或“正在观看的内容”,在本例中,您已要求它观看 props.index。每当依赖项数组中的任何内容(props.index - 在您的情况下)发生变化时,它都会 运行 在 useEffect 中运行。所以这里发生的是每次 props.index 更改时您都在更新 props.index。这是你的无限循环。

在这里结合一些东西,创建一个 props.index 的副本作为东西,即
const [something, setSomething = useState(props.index);
(我不会进入解构,但也值得一看) 你不想像现在这样直接操纵你的道具。

这解决了这个问题,并为您提供了查看 useEffect 的正确方法。由于您想在 prop 更改时更新某些内容,因此您可以在依赖项数组中保留 props.index(再次查找解构),并将 useEffect 更改为:

const [something, setSomething] = useState(props.index);

useEffect(() => {
  setSomething(props.index);
}, [props.index]);

正如另一位指出的那样,如果不知道您在做什么,这很困难,但这是一种概述,希望可以帮助您了解这里发生了什么以及为什么您会在这里遇到循环。

你提到你害怕错过清理,那么你不应该直接使用 setIndex。相反,您可以创建一个新函数来清理和设置索引。

useEffect(() => {
  setSanitizeIndex(props.index);
}, [props.index]);

const setSanitizeIndex = (value) => {
    const sanitizeIndex = sanitizeIndex(value);
    setIndex(sanitizeIndex)
}

因此,您不应再在代码中调用 setIndex,而应仅调用 setSanitizeIndex

这似乎是 custom hook 的一个好案例。这是一个如何为您的案例实施一个示例(鉴于您的问题中当前提供的信息),包括关于 how/why:

的评论

Be sure to read the documentation for useCallback if you are not already familiar with it. It's especially important to understand how to use the dependency array (link 1, link 2) when using hooks which utilize it (like useCallback and useEffect).

<div id="root"></div><script src="https://unpkg.com/react@17.0.2/umd/react.development.js"></script><script src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.development.js"></script><script src="https://unpkg.com/@babel/standalone@7.16.12/babel.min.js"></script>
<script type="text/babel" data-type="module" data-presets="env,react">

const {useCallback, useEffect, useState} = React;

/**
 * You didn't show exactly how you are sanitizing, so I'm using this function
 * instead. It will simply make sure the input number is always even by
 * adding 1 to it if it's odd.
 */
function makeEven (n) {
  return n % 2 === 0 ? n : n + 1;
}

function useSanitizedIndex (sanitizeIndex, unsanitizedIndex) {
  const [index, setIndex] = useState(sanitizeIndex(unsanitizedIndex));

  // Like setIndex, but also sanitizes
  const setSanitizedIndex = useCallback(
    (unsanitizedIndex) => setIndex(sanitizeIndex(unsanitizedIndex)),
    [sanitizeIndex, setIndex],
  );

  // Update state if arguments change
  useEffect(
    () => setSanitizedIndex(unsanitizedIndex),
    [setSanitizedIndex, unsanitizedIndex],
  );

  return [index, setSanitizedIndex];
}

function IndexComponent (props) {
  // useCallback memoizes the function so that it's not recreated on every
  // render. This also prevents the custom hook from looping infinintely
  const sanitizeIndex = useCallback((unsanitizedIndex) => {
    // Whatever you actually do to sanitize the index goes in here,
    // but I'll just use the makeEven function for this example
    return makeEven(unsanitizedIndex);
    // If you use other variables in this function which are defined in this
    // component (e.g. you mentioned an array state of some kind), you'll need
    // to include them in the dependency array below:
  }, []);

  // Now simply use the sanitized index where you need it,
  // and the setter will sanitize for you when setting it (like in the
  // click handler in the button below)
  const [index, setSanitizedIndex] = useSanitizedIndex(sanitizeIndex, props.index);

  return (
    <div>
      <div>Sanitized index (will always be even): {index}</div>
      <button onClick={() => setSanitizedIndex(5)}>Set to 5</button>
    </div>
  );
}

function Example () {
  const [count, setCount] = useState(0);
  return (
    <div>
      <div>Count: {count}</div>
      <button onClick={() => setCount(n => n + 1)}>Increment</button>
      <IndexComponent index={count} />
    </div>
  );
}

ReactDOM.render(<Example />, document.getElementById('root'));

</script>