简体   繁体   English

clearInterval 在使用功能组件的 React 应用程序中不起作用

[英]clearInterval not working in React Application using functional component

I wanted to build a timer application in React using functional component and below are the requirements.我想使用functional componentReact中构建一个计时器应用程序,以下是要求。

The component will display a number initialized to 0 know as counter .该组件将显示一个初始化为0的数字,称为counter

The component will display a Start button below the counter number.该组件将在counter编号下方显示一个Start按钮。

On clicking the Start button the counter will start running.单击“ Start ”按钮后,计数器将开始运行。 This means the counter number will start incrementing by 1 for every one second.这意味着counter编号将开始每 1 秒递增 1。

When the counter is running(incrementing), the Start button will become the Pause button.当计数器运行(递增)时, Start按钮将变为Pause按钮。

On clicking the Pause button, the counter will preserve its value (number) but stops running(incrementing).单击Pause按钮时, counter将保留其值(数字)但停止运行(递增)。

The component will also display a Reset button.该组件还将显示一个Reset按钮。 On clicking the Reset button, the counter will go to its initial value(which is 0 in our case) and stops running(incrementing).单击Reset按钮后, counter将 go 恢复为其初始值(在我们的示例中为0 )并停止运行(递增)。

Below is the code that I have implemented, but clearInterval doesn't seems to be working, Also how do i implement Reset Button?下面是我已经实现的代码,但clearInterval似乎没有工作,另外我如何实现重置按钮?

Code:代码:

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

export default function Counter() {
  const [counter, setCounter] = useState(0);
  const [flag, setFlag] = useState(false);
  const [isClicked, setClicked] = useState(false);
  var myInterval;

  function incrementCounter() {
    setClicked(!isClicked);
    if (flag) {
      myInterval = setInterval(
        () => setCounter((counter) => counter + 1),
        1000
      );
      setFlag(false);
    } else {
      console.log("sasdsad");
      clearInterval(myInterval);
    }
  }

  function resetCounter() {
    clearInterval(myInterval);
    setCounter(0);
  }

  useEffect(() => {
    setFlag(true);
  }, []);

  return (
    <div>
      <p>{counter}</p>
      <button onClick={incrementCounter}>
        {isClicked ? "Pause" : "Start"}
      </button>
      <button onClick={resetCounter}>Reset</button>
    </div>
  );
}

Codesandbox link: CodeSandbox Codesandbox 链接: CodeSandbox

I did a slightly different version that use an extra useEffect that runs on isRunning (changed name from flag ) change:我做了一个稍微不同的版本,它使用了一个在isRunning上运行的额外useEffect (从flag更改了名称)更改:

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

export default function Counter() {
  const [counter, setCounter] = useState(0);
  // Change initial value to `false` if you don't want
  // to have timer running on load
  // Changed `flag` name to more significant name
  const [isRunning, setIsRunning] = useState(false);
  // You don't need 2 variable for this
  //const [isClicked, setClicked] = useState(false);

  // Using `useRef` to store a reference to the interval
  const myInterval = useRef();

  useEffect(() => {
    // You had this line to start timer on load
    // but you can just set the initial state to `true`
    //setFlag(true);
    // Clear time on component dismount
    return () => clearInterval(myInterval.current);
  }, []);

  // useEffect that start/stop interval on flag change
  useEffect(() => {
    if (isRunning) {
      myInterval.current = setInterval(
        () => setCounter((counter) => counter + 1),
        1000
      );
    } else {
      clearInterval(myInterval.current);
      myInterval.current = null;
    }
  }, [isRunning]);

  // Now on click you only change the flag
  function toggleTimer() {
    setIsRunning((isRunning) => !isRunning);
  }

  function resetCounter() {
    clearInterval(myInterval.current);
    myInterval.current = null;
    setCounter(0);
    setIsRunning(false);
  }

  return (
    <div>
      <p>{counter}</p>
      <button onClick={toggleTimer}>{isRunning ? "Pause" : "Start"}</button>
      <button onClick={resetCounter}>Reset</button>
    </div>
  );
}

Demo:https://codesandbox.io/s/dank-night-wwxqz3?file=/src/Counter.js演示:https://codesandbox.io/s/dank-night-wwxqz3?file=/src/Counter.js

As a little extra i've made a version that uses a custom hook useTimer .作为额外的一点,我制作了一个使用自定义钩子useTimer的版本。 In this way the component code is way cleaner: https://codesandbox.io/s/agitated-curie-nkjf62?file=/src/Counter.js这样,组件代码就更干净了: https://codesandbox.io/s/agitated-curie-nkjf62?file=/src/Counter.js

Use useRef to make the interval as a ref.使用useRef将区间作为参考。 Then use resetCounter() to clean the interval ref.然后使用resetCounter()清理间隔参考。

const intervalRef = useRef(null)

const incrementCounter = () => {
  intervalRef.current = setInterval(() => {
    setCounter(prevState => prevState + 1)
  }, 1000);
};

const resetCounter = () => {
  clearInterval(intervalRef.current);
  intervalRef.current = null;
};

You have to store myInterval in state.您必须将myInterval存储在 state 中。 After that when button is clicked and flag is false , you can clear interval (myInterval in state).之后,当单击按钮且flagfalse时,您可以清除间隔(状态为 myInterval)。

Between each rendering your variable myInterval value doesn't survive.在每次渲染之间,您的变量myInterval值无法生存。 That's why you need to use the [ useRef ][1] hook that save the reference of this variable across each rendering.这就是为什么您需要使用 [ useRef ][1] 钩子在每次渲染中保存此变量的引用。

Besides, you don't need an flag function, as you have all information with the myClicked variable此外,您不需要标志 function,因为您拥有myClicked变量的所有信息

Here is a modification of your code with those modifications.这是使用这些修改对您的代码进行的修改。 Don't hesitate if you have any question.如果您有任何问题,请不要犹豫。

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

export default function Counter() {
  const [counter, setCounter] = useState(0);
  const [isStarted, setIsStarted] = useState(false);
  const myInterval = useRef();

  function start() {
    setIsStarted(true);
      myInterval.current = setInterval(() => setCounter((counter) => counter + 1), 100);
      100;
    } 

  function pause() {
    setIsStarted(false);
    clearInterval(myInterval.current);
  }

  function resetCounter() {
    clearInterval(myInterval.current);
    setCounter(0);
  }

  return (
    <div>
      <p>{counter}</p>
      {!isStarted ? 
      <button onClick={start}>
        Start
      </button> 
      :
      <button onClick={pause}>
        Pause
      </button> 
    }
      
      <button onClick={resetCounter}>Reset</button>
    </div>
  );
}
\\\


  [1]: https://reactjs.org/docs/hooks-reference.html#useref

I'll just leave this here for anyone having the same problem.我会把这个留给有同样问题的人。

in my case, the issue was node setInterval was used instead of window.setInterval.就我而言,问题是使用节点 setInterval 而不是 window.setInterval。

this is a problem since this returns a type of Node.Timer which is an object instead of number (setInterval ID) for the clearInterval() to work as it needs an argument type of number.这是一个问题,因为这会返回一个 Node.Timer 类型,它是 object 而不是数字(setInterval ID),因为 clearInterval() 需要一个参数类型的数字才能工作。 so to fix this,所以要解决这个问题,

React.useEffect(() => {
 let timeoutId;
 timeoutId = window.setInterval(callback, 100);

 return = () => {
  if(timeoutId) clearInterval(timeoutId)
 }
}, [])

or in class components use componentWillMount()或在 class 组件中使用 componentWillMount()

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

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