useEffect 的返回究竟是如何工作的?为什么代码会这样执行?

Tim*_*Jao 2 settimeout react-hooks use-effect

我正在练习反应挂钩,并且正在创建一个非常简单的秒表应用程序。目前,我的代码正在做我想要它做的事情,但我不明白它为什么起作用。当我点击开始时,setTimeouts 运行并不断更新时间状态。当我点击停止时,它会清除超时。当我没有明确告诉它时,为什么它会清除超时。另外,根据react文档,useEffect中的返回仅在组件卸载时运行。但是,我将 console.logs 放入其中,发现每次调用 useEffect 时它都会运行返回的回调。最后,我删除了返回的回调,发现当我停止时它实际上并没有清除超时。有人可以帮我剖析一下吗?

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

function Stopwatch(){

  const [time, setTime] = useState(0);
  const [start, setStart] = useState(false);

  useEffect(() => {
    let timeout;
    if (start) {
      timeout = setTimeout(() => {setTime(currTime => currTime + 1);}, 1000);
    }

    return () => {
      clearTimeout(timeout);
    }
  });

  return(
    <>
      <div>{time}</div>
      <button onClick={() => setStart(currStart => !currStart)}>{start ? "Stop" : "Start"}</button>
    </>
  )
}

export default Stopwatch
Run Code Online (Sandbox Code Playgroud)

Asa*_*viv 8

当我没有明确告诉它时,为什么它会清除超时?

在您的实现中,useEffect每次重新渲染后都会运行,因为您没有指定依赖项数组,因此如果您启动计时器,然后在中间按停止,则清理功能将运行,并且最后的超时将被清除

事情是这样的,

组件安装 ->useEffect回调触发并返回一个函数 -> 当组件重新渲染时,返回的函数被执行,循环返回到运行回调useEffect

您可能在文档中读到的内容有一个空的依赖项数组,它是第二个参数useEffect

useEffect(() => {
  console.log('will only run when the component mounts for the first time')

  return () => {
    console.log('will only run when the component unmounts')
  }
}, []) // nothing inside the dependencies array, run this once
Run Code Online (Sandbox Code Playgroud)

组件的更好实现将是这样的

function Stopwatch(){
  const [time, setTime] = useState(0)
  const [start, setStart] = useState(false)

  useEffect(() => {
    // when start is false there is no reason to set up a timer or return a
    // cleanup function so lets just exit early
    if (!start) return 

    // start is true, set up the interval
    const intervalId = setInterval(() => setTime(prevTime => prevTime + 1), 1000)
    // return a cleanup function that will run only when start changes
    // to false
    return () => clearInterval(intervalId)
  }, [start]) // run this effect only when start changes

  const toggleStart = () => setStart(prevStart => !prevStart)

  return(
    <>
      <div>{time}</div>
      <button onClick={toggleStart}>{start ? "Stop" : "Start"}</button>
    </>
  )
}
Run Code Online (Sandbox Code Playgroud)