cheungseol / cheungseol.github.io

2 stars 0 forks source link

[React] React Hooks 中使用 setTimeout #20

Open cheungseol opened 5 years ago

cheungseol commented 5 years ago

问题

在hooks 中使用 setTimeout 方法,方法中访问到的函数 state 始终是初始值,而不是更新后的最新 state

demo

在这个例子中,首先执行setCount 将 count 设为 5, 然后经过 3 秒后执行 setCountInTimeout, 将 countInTimeout 的值设置为count 的值

我们最初期望的是 这时候 countInTimeout 就等于 此刻 count 最新的值 5, 然而 countInTimeout 却保持了最开始的 count 值 0

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

const TimeoutExample = () => {
  const [count, setCount] = useState(0);
  const [countInTimeout, setCountInTimeout] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      setCountInTimeout(count); // count is 0 here
    }, 3000);
    setCount(5); // Update count to be 5 after timeout is scheduled
  }, []);

  return (
    <div>
      Count: {count}
      <br />
      setTimeout Count: {countInTimeout}
    </div>
  );
};

export default TimeoutExample;

原因

setTimeout 是一个闭包,setTimeout 函数执行的时候使用的参数 count 读取自setTimeout 函数创建的时候,即 0。 setTimeout 使用闭包的方式异步访问 count 的值。当整个函数组件re-render的时候,会创建出一个新的 setTimeout 函数一个新的闭包,但并没有改变最初封装它的那个闭包的值

作者也提到这么设计的初衷是满足这样的场景:比如订阅了一个ID,当随后需要取消订阅的时候,避免ID发生变化而造成不能取消订阅的问题

解决方法

使用一个 container 来把最新的 state 也就是 count 的值穿进去,并在随后的 timeout 函数中读取最新的 state 。

可以使用 useRef。 通过 ref's current 来同步最新的 state, 然后在 timeout 函数中读取 current 的值。使用 ref 在异步callback函数中访问最新的 当前的 state

const countRef = useRef(count);
countRef.current = count;

const getCountTimeout = () => {
  setTimeout(() => {
    setTimeoutCount(countRef.current);
  }, 2000);
};

参考State from useState hook inside a setTimeout is not updated

jim-kk-hc commented 2 years ago

useCallback deps 加上 count?