繁体   English   中英

React hooks:当 state 本身是依赖项时,如何在 useEffect 中更新 state ?

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

我知道已经有一些相关的问题,比如React useEffect 如何监视和更新 state? ,但是,我还是不完全明白。

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

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

它不起作用(无限循环),因为 state 由第二个useEffect()监视更新。

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

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

问题是,我在代码中多次调用setIndex ,我担心会错过使用sanitizeIndex

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

谢谢 !

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

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

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

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

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

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

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

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

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

有了这个,你不应该在你的代码中再调用setSanitizeIndex setIndex

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

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

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

我过去针对类似问题所做的一个潜在解决方案是间接触发 useEffect。 创建一个与正在更新的 state 无关的虚拟 state,并在您希望触发效果时更新虚拟 state。

const [dummyState, setDummyState] = useState(0)
useEffect(() => {
  setIndex(props.index);
}, [props.index]);

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

const sanitizeIndex = index => {
    //check that index exists in array...
    //use fallback if not...
    //etc.
    return index
}
return (
    //update the state you want to update then update the dummy state to 
    //trigger the useEffect
    <button onClick={() =>{setIndex(123);
                           setDummyState(dummyState++);}/>
)

我认为接受的答案解决方案不那么笨拙,但在更复杂的情况下,这可能是您最简单和最容易理解的解决方案

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM