React hooks - 清除超时和间隔的正确方法

新手上路,请多包涵

我不明白为什么当我使用 setTimeout 函数时,我的反应组件开始到无限的 console.log。一切正常,但 PC 开始滞后。

有人说这个函数在超时改变我的状态和重新渲染组件,设置新的计时器等等。现在我需要了解如何清除它是正确的。

export default function Loading() {
  // if data fetching is slow, after 1 sec i will show some loading animation
  const [showLoading, setShowLoading] = useState(true)
  let timer1 = setTimeout(() => setShowLoading(true), 1000)

  console.log('this message will render  every second')
  return 1
}

清除不同版本的代码无助于:

const [showLoading, setShowLoading] = useState(true)
  let timer1 = setTimeout(() => setShowLoading(true), 1000)
  useEffect(
    () => {
      return () => {
        clearTimeout(timer1)
      }
    },
    [showLoading]
  )

原文由 ZiiMakc 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 974
2 个回答

定义的 return () => { /*code/* } 函数在 useEffect 每次运行时 useEffect 运行(组件挂载时第一次渲染除外)和组件卸载时(如果您不再显示组件)。

这是一种使用和清除超时或间隔的工作方式:

沙盒示例

 import { useState, useEffect } from "react";

 const delay = 5;

 export default function App() {
 const [show, setShow] = useState(false);

 useEffect(
 () => {
 let timer1 = setTimeout(() => setShow(true), delay * 1000);

 // this will clear Timeout
 // when component unmount like in willComponentUnmount
 // and show will not change to true
 return () => {
 clearTimeout(timer1);
 };
 },
 // useEffect will run only one time with empty []
 // if you pass a value to array,
 // like this - [data]
 // than clearTimeout will run every time
 // this value changes (useEffect re-run)
 []
 );

 return show ? (
 <div>show is true, {delay}seconds passed</div>
 ) : (
 <div>show is false, wait {delay}seconds</div>
 );
 }

如果您需要清除另一个组件中的超时或间隔:

沙盒示例。

 import { useState, useEffect, useRef } from "react";

 const delay = 1;

 export default function App() {
 const [counter, setCounter] = useState(0);
 const timer = useRef(null); // we can save timer in useRef and pass it to child

 useEffect(() => {
 // useRef value stored in .current property
 timer.current = setInterval(() => setCounter((v) => v + 1), delay * 1000);

 // clear on component unmount
 return () => {
 clearInterval(timer.current);
 };
 }, []);

 return (
 <div>
 <div>Interval is working, counter is: {counter}</div>
 <Child counter={counter} currentTimer={timer.current} />
 </div>
 );
 }

 function Child({ counter, currentTimer }) {
 // this will clearInterval in parent component after counter gets to 5
 useEffect(() => {
 if (counter < 5) return;

 clearInterval(currentTimer);
 }, [counter, currentTimer]);

 return null;
 }

丹·阿布拉莫夫的文章

原文由 ZiiMakc 发布,翻译遵循 CC BY-SA 4.0 许可协议

问题是你在调用 setTimeout 外面 useEffect ,所以你每次渲染组件时都设置一个新的超时,最终将再次调用并改变状态,强制组件再次重新渲染,这将设置一个新的超时,这……

因此,正如您已经发现的那样,使用 setTimeoutsetInterval 的方法是将它们包装在 useEffect 中:

 React.useEffect(() => {
    const timeoutID = window.setTimeout(() => {
        ...
    }, 1000);

    return () => window.clearTimeout(timeoutID );
}, []);

作为 deps = [] , useEffect 的回调只会被调用一次。然后,您返回的回调将在卸载组件时被调用。

无论如何,我鼓励您创建自己的 useTimeout 挂钩,以便您可以通过使用 setTimeout 声明性 地干燥和简化代码,正如 Dan Abramov 建议的 setIntervalMaking setInterval Declarative with React Hooks 中,非常相似:

 function useTimeout(callback, delay) {
  const timeoutRef = React.useRef();
  const callbackRef = React.useRef(callback);

  // Remember the latest callback:
  //
  // Without this, if you change the callback, when setTimeout kicks in, it
  // will still call your old callback.
  //
  // If you add `callback` to useEffect's deps, it will work fine but the
  // timeout will be reset.

  React.useEffect(() => {
    callbackRef.current = callback;
  }, [callback]);

  // Set up the timeout:

  React.useEffect(() => {
    if (typeof delay === 'number') {
      timeoutRef.current = window.setTimeout(() => callbackRef.current(), delay);

      // Clear timeout if the components is unmounted or the delay changes:
      return () => window.clearTimeout(timeoutRef.current);
    }
  }, [delay]);

  // In case you want to manually clear the timeout from the consuming component...:
  return timeoutRef;
}

const App = () => {
  const [isLoading, setLoading] = React.useState(true);
  const [showLoader, setShowLoader] = React.useState(false);

  // Simulate loading some data:
  const fakeNetworkRequest = React.useCallback(() => {
    setLoading(true);
    setShowLoader(false);

    // 50% of the time it will display the loder, and 50% of the time it won't:
    window.setTimeout(() => setLoading(false), Math.random() * 4000);
  }, []);

  // Initial data load:
  React.useEffect(fakeNetworkRequest, []);

  // After 2 second, we want to show a loader:
  useTimeout(() => setShowLoader(true), isLoading ? 2000 : null);

  return (<React.Fragment>
    <button onClick={ fakeNetworkRequest } disabled={ isLoading }>
      { isLoading ? 'LOADING... 📀' : 'LOAD MORE 🚀' }
    </button>

    { isLoading && showLoader ? <div className="loader"><span className="loaderIcon">📀</span></div> : null }
    { isLoading ? null : <p>Loaded! ✨</p> }
  </React.Fragment>);
}

ReactDOM.render(<App />, document.querySelector('#app'));
 body,
button {
  font-family: monospace;
}

body, p {
  margin: 0;
}

#app {
  display: flex;
  flex-direction: column;
  align-items: center;
  min-height: 100vh;
}

button {
  margin: 32px 0;
  padding: 8px;
  border: 2px solid black;
  background: transparent;
  cursor: pointer;
  border-radius: 2px;
}

.loader {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 128px;
  background: white;
}

.loaderIcon {
  animation: spin linear infinite .25s;
}

@keyframes spin {
  from { transform:rotate(0deg) }
  to { transform:rotate(360deg) }
}
 <script src="https://unpkg.com/react@16.7.0-alpha.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7.0-alpha.0/umd/react-dom.development.js"></script>

<div id="app"></div>

除了生成更简单和更清晰的代码之外,这还允许您通过传递 delay = null 自动清除超时,并返回超时 ID,以防您想要自己手动取消它(Dan 的帖子中未涵盖)。

如果您正在寻找 setInterval 而不是 setTimeout 的类似答案,请查看: https ://stackoverflow.com/a/59274004/3723993。

You can also find declarative version of setTimeout and setInterval , useTimeout and useInterval , a few additional hooks written in TypeScript in https:/ /www.npmjs.com/package/@swyg/corre

原文由 Danziger 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题