【问题标题】:clearInterval() not working in React Native functional componentclearInterval() 在 React Native 功能组件中不起作用
【发布时间】:2020-08-09 13:10:34
【问题描述】:

我有一个屏幕组件,它有一个 getPosition() 函数,每隔一段时间就会调用一次。

如果调用了stopRace() 函数,或者如果用户按下物理/图形后退按钮,我想清除此间隔,使其不会继续在后台运行。

为此,我尝试将间隔 ID 存储在 raceUpdateInterval 状态变量中。

然后我在stopRace() 函数和cleanup() 函数中使用clearInterval(raceUpdateInterval) 清除此间隔。

当我调用stopRace() 函数,然后按回,间隔被清除。我知道这一点是因为我的控制台日志:

Still Running
Still Running
Still Running
Reached cleanup function

但是,如果我按下后退按钮,间隔不会清除。相反,我的控制台日志:

Still Running
Still Running
Still Running
Reached cleanup function
Still Running

随后是包含以下建议的内存泄漏警告:

To fix, cancel all subscriptions and asynchronous tasks in %s.%s, a useEffect cleanup function

这正是我想要做的,但由于某种超出我理解的原因无法正常工作。

这里是组件的相关代码:

const RaceScreen = ({route, navigation}) => {

    const [raceUpdateInterval, setRaceUpdateInterval] = useState(0);

    useEffect(function() {
        return function cleanup() {
            console.log('Reached cleanup function')
            clearInterval(raceUpdateInterval)
        }
      }, []);

    function getPosition(){
        console.log("Still being called")
        //get position
    }

    function startRace(){
        setRaceUpdateInterval(setInterval(getPosition, 1000))
    }

    function stopRace(){
        clearInterval(raceUpdateInterval)
    }

为什么stopRace() 函数能正确清除区间而cleanup() 函数不能?

【问题讨论】:

    标签: javascript react-native react-hooks


    【解决方案1】:

    您的代码可能无法按原样运行的部分原因是,如果您多次运行 startRace 函数而不在其间停止它,则间隔将再次启动,但间隔 ID 将迷路了。

    没有清除的主要原因是,当useEffect以[]作为依赖数组时,它一开始看到的raceUpdateInterval是:0。它没有看到更新值的原因是因为 useEffect 在它运行(并重新运行)的点创建了一个闭包。所以你需要使用一个引用来让它访问最新版本的raceUpdateInterval

    以下是我将如何修改您的代码以使其正常工作的方法。不要在函数中启动计时器,而是使用useEffect 来启动那个副作用,这样就永远不会出现计时器无法清理的情况。

    我使用 ref 将函数添加到区间,因为我不知道 getPosition 函数中有多少个闭包变量。这样, positionFunctRef.current 总是指向函数的最新版本,而不是保持静态。

    const RaceScreen = ({ route, navigation }) => {
      const [runningTimer, setRunningTimer] = useState(false);
      function getPosition() {
        console.log('Still being called');
        //get position
      }
      const positionFunctRef = useRef(getPosition);
      useEffect(() => {
        positionFunctRef.current = positionFunctRef;
      });
    
      useEffect(
        function () {
          if (!runningTimer) {
            return;
          }
    
          const intervalId = setInterval(() => {
            positionFunctRef.current();
          }, 1000);
          return () => {
            console.log('Reached cleanup function');
            clearInterval(intervalId);
          };
        },
        [runningTimer]
      );
    
      function startRace() {
        setRunningTimer(true);
      }
    
      function stopRace() {
        setRunningTimer(false);
      }
    };
    

    【讨论】:

      【解决方案2】:

      componentWillUnmount 用于清理(如删除事件侦听器、取消计时器等)。假设您要在 componentDidMount 中添加一个事件侦听器,并在 componentWillUnmount 中删除它,如下所示。

      componentDidMount() {
        window.addEventListener('mousemove', () => {})
      }
      
      componentWillUnmount() {
        window.removeEventListener('mousemove', () => {})
      }
      

      上面代码的钩子等效如下

      useEffect(() => {
        window.addEventListener('mousemove', () => {});
      
        // returned function will be called on component unmount 
        return () => {
          window.removeEventListener('mousemove', () => {})
        }
      }, [])
      

      所以你更好的代码是:

       useEffect(function() {
        setRaceUpdateInterval(setInterval(getPosition, 1000))
              return function cleanup() {
                  console.log('Reached cleanup function')
                  clearInterval(raceUpdateInterval)
              }
            }, []);
      

      【讨论】:

        【解决方案3】:

        不要在 state 中存储间隔 id 之类的东西,因为每次更新都会重新渲染。如果您可以使用,请使用useRef() 实现setInterval,如果基于类,请使用this.interval

        另一个问题是在 ref 的功能组件中调用 clearInterval(),而不是 .current

        这是我刚刚调试的一个 sn-p:

          const spinnerCount = useRef(0)
          const interval = useRef(null)
        
          useEffect(() => {
            if (withProgress && inProgress && notification == '') {
              interval.current = setInterval(() => {
                if (spinnerCount.current >= 40) {
                  clearInterval(interval.current)
                  spinnerCount.current = 0
                  setNotification('Something happened... Please try again.')
                } else {
                  spinnerCount.current = spinnerCount.current + 1
                }
              }, 1000)
            } 
            if (notification !== '' && inProgress === false) {
              const delay = notification.length > 100 ? 6000 : 3000
              setTimeout(() => {
                clearInterval(interval.current)
                spinnerCount.current = 0
                setNotification('');
              }, delay);
            }
          }, [inProgress])
        

        还有一些额外的东西,基本上这是一个消失的通知组件,它还具有一个进度微调器。在这种情况下,如果组件正在显示微调器,但从未触发成功/错误通知,则微调器将在 40 秒后自动退出。因此间隔/微调器计数

        【讨论】:

          猜你喜欢
          • 2022-06-11
          • 2021-07-16
          • 2022-09-23
          • 2021-10-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-10-31
          • 1970-01-01
          相关资源
          最近更新 更多