【问题标题】:React hooks : how can I update a state within useEffect when the state itself is the dependency?React hooks:当状态本身是依赖项时,如何在 useEffect 中更新状态?
【发布时间】:2022-01-26 19:06:08
【问题描述】:

我知道已经有一些相关的问题,比如How can React useEffect watch and update state?,但我还是不完全明白。

假设我基于一个道具设置了一个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

还有其他方法可以“捕捉”并更新正在设置的状态值吗?

谢谢!

【问题讨论】:

  • 如果您可以展示props.index的示例(并包括index的类型:例如string/number/等)并展示sanitizeIndex的实现,你会得到更好的答案。
  • 嗨@jsejcksn,我已经改进了这个问题。

标签: reactjs state use-effect


【解决方案1】:

这对于custom hook 来说似乎是一个很好的案例。以下是如何为您的案例实施一个示例(鉴于您的问题中当前提供的信息),包括有关如何/为什么的 cmets:

如果您还不熟悉useCallback,请务必阅读它的文档。在使用依赖数组(link 1link 2)时,了解如何使用依赖数组(如useCallbackuseEffect)尤为重要。

<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>

【讨论】:

  • 感谢您的好答案,看起来非常好,我测试了它并且它有效。但是,这很难理解和阅读。 @Someone Special 的答案似乎更容易。你怎么看?
  • @gordie 好吧,你还没有展示你的实际组件代码,所以我真的不能说。我的答案将添加inside 您的组件的唯一代码是const [index, setSanitizedIndex] = useSanitizedIndex(sanitizeIndex, props.index);(它替换您使用useState 创建状态的行)。 “易读”由你决定!
  • @gordie 关于另一个答案:正如所写,它会引发两个运行时错误。
  • 非常感谢您的帮助!
  • 我仍在努力理解这一切。我的状态无法正确更新。你介意看看pastebin.com/rWGQD8iM 吗?
【解决方案2】:

所以将 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]);

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

【讨论】:

    【解决方案3】:

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

    useEffect(() => {
      setSanitizeIndex(props.index);
    }, [props.index]);
    
    const setSanitizeIndex = (value) => {
        const sanitizeIndex = sanitizeIndex(value);
        setIndex(sanitizeIndex)
    }
    

    这样,您不应再在代码中调用setIndex,而只能调用setSanitizeIndex

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-11-28
      • 2021-02-09
      • 2019-11-01
      • 1970-01-01
      • 2022-12-30
      • 1970-01-01
      • 2019-08-08
      相关资源
      最近更新 更多