【问题标题】:How to use an `onClick` event to stop calling a function which is called by the useEffect hook?如何使用 `onClick` 事件停止调用由 useEffect 挂钩调用的函数?
【发布时间】:2020-07-31 02:33:22
【问题描述】:

如何使用onClick 事件来停止重复的函数调用?由于使用了useEffectsetIntervalclearInterval,函数调用重复出现。

this article 中显示的示例,下面的代码将永远运行。单击<header></header>后如何停止调用该函数?

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

const IntervalExample = () => {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(seconds => seconds + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);

  return (
    <div className="App">
      <header className="App-header">
        {seconds} seconds have elapsed since mounting.
      </header>
    </div>
  );
};

export default IntervalExample;

【问题讨论】:

  • 你基本上不能,因为你声明它是一个 const ,没有任何东西可以改变他在 useEffect 中的值,所以没有什么能达到它。但是您可以在使用效果之外声明它,并使用 seconds 上的 useEffect 或类似的东西来更改 interval 的值。

标签: reactjs react-hooks setinterval clearinterval use-effect


【解决方案1】:

添加另一个名为running 的状态,并使useEffect 依赖它。每当running 切换到false 时,useEffect 的清理功能将清除间隔。由于runningfalse,我们跳过设置新间隔。只要切换到true,它就会重新启动。

const { useState, useEffect, useCallback } = React;

const IntervalExample = () => {
  const [seconds, setSeconds] = useState(0);
  const [running, setRunning] = useState(true);
  
  const toggleRunning = useCallback(
    () => setRunning(run => !run)
  , []);

  useEffect(() => {
    if(!running) {
      // setSeconds(0); // if you want to reset it as well
      return;
    }
    
    const interval = setInterval(() => {
      setSeconds(seconds => seconds + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, [running]);

  return (
    <div className="App">
      <header className="App-header">
        {seconds} seconds have elapsed since mounting.
      </header>
      
      <button
        onClick={toggleRunning}
        >
        {running ? 'running' : 'stopped'}
      </button>
    </div>
  );
};

ReactDOM.render(
  <IntervalExample />,
  root
);
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>

<div id="root"></div>

【讨论】:

  • 对这种方法有疑问,当我们更新running 钩子时,它不会卸载组件,因此清理将不起作用。此时你会生成一个新的区间id,旧的区间不会被清除。我说的对吗?
  • 每当出现dependencies change 时都会调用清理:“此外,如果一个组件多次渲染(通常如此),则在执行下一个效果之前会清理上一个效果。”。如果不是,setInterval 将继续调用setSeconds
【解决方案2】:

我会像这样从 useEffect 函数中提取区间 const。

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

const IntervalExample = () => {
  const [seconds, setSeconds] = useState(0);
  let interval;

  useEffect(() => {
    interval = setInterval(() => {
      setSeconds(seconds => seconds + 1);
    }, 1000);
    return () => clearInterval(interval);
  }, []);


  const handleClick = () => {
    if (interval) {
      clearInterval(interval);
    }
  }

  return (
    <div className="App">
      <header className="App-header" onClick={() => handleClick()}>
        {seconds} seconds have elapsed since mounting.
      </header>
    </div>
  );
};

export default IntervalExample;

【讨论】:

  • interval 似乎在 handleClick 函数中未定义,上面的代码似乎不起作用
【解决方案3】:

我认为停止你的意思是在点击事件上说clearInterval。因此,在新状态变量中保存间隔 id 的最佳方法。


const IntervalExample = () => {
  const [seconds, setSeconds] = useState(0);
  const [currentIntervalId, setIntervalId] = useState(null);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(seconds => seconds + 1);
    }, 1000);
    setIntervalId(interval);
    return () => clearInterval(interval);
  }, []);

  const stopInterval = () => {
    clearInterval(currentIntervalId);
  };

  return (
    <div className="App">
      <header className="App-header" onClick={stopInterval}>
        {seconds} seconds have elapsed since mounting.
      </header>
    </div>
  );
};
  • 为什么在状态? 因为这将使您更灵活地重新开始间隔或根据事件随时停止。

注意:你也可以把它放在useEffect范围之外定义的变量中。

【讨论】:

    猜你喜欢
    • 2019-08-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-11-01
    • 2021-07-04
    • 2014-02-24
    • 2020-09-14
    相关资源
    最近更新 更多