react hooks useState值为什么没有生效?

今天在工作中遇到一个react hooks问题,虽然解决了但是对于其中的原因不太理解,这里发出来和大家一起讨论一下。
具体代码如下,场景步骤如下:

  1. 点击一次,执行running后,isPolling设为true,并触发polling的useEffect
  2. 快速再点击一次,执行running,因为running前执行了cancle,所以这时候running内的isPolling应该是false,但实际还是true

看了一些批量更新的知识,好像解释不了这种现象。大家有什么看法吗?

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

export default function MyApp() {
  const [isPolling, setIsPolling] = useState(false);
  const [pollingCount, setPollingCount] = useState(0);
  const [timer, setTimer] = useState();

  const handleClick = () => {
    cancle();
    running();
  };

  useEffect(() => {
    if (isPolling && pollingCount <= 3) {
      console.log("33");
      clearTimeout(timer);
      setTimer(
        setTimeout(() => {
          console.log("44");
          running();
        }, 1000)
      );
    }
  }, [isPolling, pollingCount]);

  const running = useCallback(() => {
    console.log("running11:", isPolling, pollingCount);

    if (!isPolling) {
      console.log("11");
      setIsPolling(true);
    } else if (isPolling) {
      console.log("22");
      setPollingCount(pollingCount + 1);
    }
  }, [isPolling, pollingCount]);

  const cancle = () => {
    setIsPolling(false);
    setPollingCount(0);
  };

  return (
    <div>
      <div onClick={handleClick}>点击</div>
    </div>
  );
}

执行结果如下

阅读 2.6k
3 个回答

其实这个问题很基础,甚至还不涉及到任何框架,这里我用最简单的模型来简化一下你这个问题

const [isPolling, setIsPolling] = useState(true);

const handleClick = () => {
    setIsPolling(false)
    console.log(isPolling); // true
};

isPolling是一个常量,在同一个作用域内,无论使用什么方式,都不可能实现后续去改变它的值,这就回答了你的疑问,为什么调用 setIsPolling(false) 后,isPolling的值还是 true

其实每次更新 useState 定义的状态,都会驱动 函数组件 重新执行,在重新执行的作用域中,isPolling的值才是更新后的值,

那么在更新之前的上一次作用域中如何确保拿到更新后的值呢,React也提供了相应的方式,使用 setter 的回调

setIsPolling((prevIsPolling) => {
    console.log(isPolling); // false
})

React内部会在组件对应的 fiber 结构上维护一个更新队列,对于同一个状态连续的 setState 调用会形成一个循环链表,将上一次 setState 的结果传递给下一个 setState 的回调,因此通过回调的方式你能拿到上一次 setIsPolling 的新值

const running = useCallback(() => {
    setIsPolling((currentIsPolling) => {
        console.log("running11:", currentIsPolling, pollingCount);

        if (!currentIsPolling) {
            console.log("11");
            return true;
        } else {
            console.log("22");
            setPollingCount(pollingCount + 1);
            return currentIsPolling; // return current value without change
        }
    });
}, [pollingCount]);

新手上路,请多包涵
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题