使用useEffect

时间:2020-10-23 01:27:17

标签: javascript reactjs callback react-functional-component

我遇到了从useEffect内部设置间隔计时器的情况。我可以在useEffect中访问组件变量和状态,并且间隔计时器按预期运行。但是,计时器回调无法访问组件变量/状态。通常,我希望这是“ this”的问题。但是,我不认为“这种情况”就是这种情况。没有双关语的意图。我在下面提供了一个简单的示例:

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

const App = () => {
  const [count, setCount] = useState(0);
  const [intervalSet, setIntervalSet] = useState(false);

  useEffect(() => {
    if (!intervalSet) {
      setInterval(() => {
        console.log(`count=${count}`);
        setCount(count + 1);
      }, 1000);
      setIntervalSet(true);
    }
  }, [count, intervalSet]);

  return <div></div>;
};

export default App;

控制台每秒仅输出count = 0。我知道有一种方法可以将函数传递给setCount,该函数可以更新当前状态,并且可以在此简单示例中使用。但是,这不是我要提出的重点。实际的代码比我在这里显示的要复杂得多。我的真实代码查看由异步重击操作管理的当前状态对象。另外,我知道在卸下组件时没有包括清理功能。对于这个简单的示例,我并不需要它。

3 个答案:

答案 0 :(得分:1)

第一次运行useEffect时,intervalSet变量被设置为true,并且间隔函数是使用当前值(0)创建的。

useEffect的后续运行中,由于进行intervalSet检查,它不会重新创建间隔,而是继续运行现有间隔,其中count是原始值(0)。

您正在使它变得比所需的复杂。

useState集合函数可以采用传递状态的当前值并返回新值的函数,即setCount(currentValue => newValue);

在卸载组件时,应始终清除间隔,否则在尝试设置状态并且状态不再存在时会遇到问题。

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

const App = () => {
    // State to hold count.
    const [count, setCount] = useState(0);

    // Use effect to create and clean up the interval 
    // (should only run once with current dependencies)
    useEffect(() => {
        // Create interval get the interval ID so it can be cleared later.
        const intervalId = setInterval(() => {
            // use the function based set state to avoid needing count as a dependency in the useEffect.
            // this stops the need to code logic around stoping and recreating the interval.
            setCount(currentCount => {
                console.log(`count=${currentCount}`);
                return currentCount + 1;
            });
        }, 1000);

        // Create function to clean up the interval when the component unmounts.
        return () => {
            if (intervalId) {
                clearInterval(intervalId);
            }
        }
    }, [setCount]);

  return <div></div>;
};

export default App;

您可以运行代码,然后在下面看到它的工作。

const App = () => {
    // State to hold count.
    const [count, setCount] = React.useState(0);

    // Use effect to create and clean up the interval 
    // (should only run once with current dependencies)
    React.useEffect(() => {
        // Create interval get the interval ID so it can be cleared later.
        const intervalId = setInterval(() => {
            // use the function based set state to avoid needing count as a dependency in the useEffect.
            // this stops the need to code logic around stoping and recreating the interval.
            setCount(currentCount => {
                console.log(`count=${currentCount}`);
                return currentCount + 1;
            });
        }, 1000);

        // Create function to clean up the interval when the component unmounts.
        return () => {
            if (intervalId) {
                clearInterval(intervalId);
            }
        }
    }, [setCount]);

  return <div></div>;
};

ReactDOM.render(<App />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>

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

答案 1 :(得分:0)

问题是间隔仅创建一次,并一直指向相同的状态值。我的建议是-将触发间隔移到useEffect之间,以便它在组件安装时开始。将时间间隔存储在变量中,以便您可以重新启动或清除它。最后-清除所有卸载。

const App = () => {
  const [count, setCount] = React.useState(0);
  const [intervalSet, setIntervalSet] = React.useState(false);

  React.useEffect(() => {
    setIntervalSet(true);
  }, []);

  React.useEffect(() => {
    const interval = intervalSet ? setInterval(() => {
      setCount((c) => {
         console.log(c);
         return c + 1;
      });
    }, 1000) : null;

    return () => clearInterval(interval);
  }, [intervalSet]);

  return null;
};

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

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

答案 2 :(得分:0)

如果您需要一个更复杂的实现(如in your comment on another answer),则应该尝试使用ref。例如,这是我在项目中使用的自定义间隔挂钩。您会发现有一种效果可以在回调发生变化时进行更新。

这可确保您始终拥有最新的状态值,并且无需使用[Pool0] volume_backend_name = VMPool0-backend volume_driver = cinder.volume.drivers.rbd.RBDDriver rbd_flatten_volume_from_snapshot = true rbd_max_clone_depth = 5 rbd_store_chunk_size = 4 rados_connect_timeout = -1 rbd_ceph_conf = /etc/ceph/ceph.conf rbd_user = cinder rbd_pool = Pool0 rbd_secret_uuid = bc19316a-6e36-4511-82ad-9b34a9d381b5 [Pool1] volume_backend_name = VMPool1-backend volume_driver = cinder.volume.drivers.rbd.RBDDriver rbd_flatten_volume_from_snapshot = true rbd_max_clone_depth = 5 rbd_store_chunk_size = 4 rados_connect_timeout = -1 rbd_ceph_conf = /etc/ceph/ceph.conf rbd_user = cinder rbd_pool = Pool1 rbd_secret_uuid = bc19316a-6e36-4511-82ad-9b34a9d381b5 之类的自定义更新程序函数语法。

setCount(count => count + 1)

这是您可以使用的非常灵活的选项。但是,此挂钩假定您要在组件安装时开始间隔。您的代码示例使我相信您希望此操作基于const useInterval = (callback, delay) => { const savedCallback = useRef() useEffect(() => { savedCallback.current = callback }, [callback]) useEffect(() => { if (delay !== null) { const id = setInterval(() => savedCallback.current(), delay) return () => clearInterval(id) } }, [delay]) } // Usage const App = () => { useInterval(() => { // do something every second }, 1000) return (...) } 布尔值的状态更改而开始。您可以更新自定义间隔挂钩,或在您的组件中实现。

在您的示例中如下所示:

intervalSet
const useInterval = (callback, delay, initialStart = true) => {
  const [start, setStart] = React.useState(initialStart)
  const savedCallback = React.useRef()

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

  React.useEffect(() => {
    if (start && delay !== null) {
      const id = setInterval(() => savedCallback.current(), delay)
      return () => clearInterval(id)
    }
  }, [delay, start])
  
  // this function ensures our state is read-only
  const startInterval = () => {
    setStart(true)
  }
  
  return [start, startInterval]
}

const App = () => {
  const [countOne, setCountOne] = React.useState(0);
  const [countTwo, setCountTwo] = React.useState(0);
  
  const incrementCountOne = () => {
    setCountOne(countOne + 1)
  }
  
  const incrementCountTwo = () => {
    setCountTwo(countTwo + 1)
  }
  
  // Starts on component mount by default
  useInterval(incrementCountOne, 1000)

  // Starts when you call `startIntervalTwo(true)`
  const [intervalTwoStarted, startIntervalTwo] = useInterval(incrementCountTwo, 1000, false)

  return (
    <div>
      <p>started: {countOne}</p>
      <p>{intervalTwoStarted ? 'started' : <button onClick={startIntervalTwo}>start</button>}: {countTwo}</p>
    </div>
  );
};

ReactDOM.render(<App />, document.getElementById('app'))