React Hooks 状态变量在 setTimeout 函数期间未更新

React Hooks state variable not updated during setTimeout function

我有一个页面正在使用从数组中提取的日志消息更新模拟 'console'。它的工作方式是每隔 n 秒向日志中添加一条新消息。

很简单吧?只需使用 setTimout 函数即可。没有钩子!

这是一些(删节的)代码:

const [logIndex, setLogIndex] = useState(0);
const [logs, setLogs] = useState([]);

let interval = useRef(setTimeout(() => {}, 0);

useEffect(() => {
    interval.current = setTimeout(addNextLogMessage, 2000);
    return(() => {
        clearTimeout(interval.current); // cleanup
    }
}, []); // run when page first loads

const sourceLogs = [
    <p>Message 1</p>,
    <p>Message 2</p>,
    <p>Message 3</p>,
    <p>Message 4</p>,
    <p>Message 5</p>,
    <p>Message 6</p>
];

const timeBetweenMessages = Math.ceil((1000 * 60 * 3) / sourceLogs.length); // three minutes (1000ms * 60sec * 3min) divided into number of log messages, rounded up

const addNextLogMessage () => {
    setLogIndex(logIndex => logIndex + 1);
    clearTimeout(interval.current);
    if(logIndex < sourceLogs.length) {
        setLogs(logs => [...logs, {...sourceLogs[logIndex], timeStamp: new Date().toString()}]);
        interval.current = setTimeout(addNextLogMessage, Math.random() * timeBetweenMessages + 500);
    }
}

return (
    <>
        {
            logs.map(item => item);
        }
    </>
);

我显然在这里做了一些愚蠢的事情,因为 logIndex 的值始终为 0。有人有任何指示吗?

我用codesandbox为你做了一个解决方案。这种行为是必需的吗?我只是遵循了控制台警告建议,仅此而已:

https://codesandbox.io/s/late-tree-01bqh?file=/src/App.js

这是其中的代码:

import React, {
  useMemo,
  useCallback,
  useState,
  useRef,
  useEffect
} from "react";
import "./styles.css";

export default function App() {
  const [logIndex, setLogIndex] = useState(0);
  const [logs, setLogs] = useState([]);

  const sourceLogs = useMemo(
    () => [
      <p>Message 1</p>,
      <p>Message 2</p>,
      <p>Message 3</p>,
      <p>Message 4</p>,
      <p>Message 5</p>,
      <p>Message 6</p>
    ],
    []
  );

  let interval = useRef(setTimeout(() => {}, 0));

  const timeBetweenMessages = Math.ceil((1000 * 60 * 3) / sourceLogs.length); // three minutes (1000ms * 60sec * 3min) divided into number of log messages, rounded up

  const addNextLogMessage = useCallback(() => {
    setLogIndex((logIndex) => logIndex + 1);
    clearTimeout(interval.current);
    if (logIndex < sourceLogs.length) {
      setLogs((logs) => [
        ...logs,
        { ...sourceLogs[logIndex], timeStamp: new Date().toString() }
      ]);
      interval.current = setTimeout(
        addNextLogMessage,
        Math.random() * timeBetweenMessages + 500
      );
    }
  }, [logIndex, sourceLogs, timeBetweenMessages]);

  useEffect(() => {
    interval.current = setTimeout(addNextLogMessage, 2000);
    return () => {
      clearTimeout(interval.current); // cleanup
    };
  }, [addNextLogMessage]);

  return (
    <>
      {logs.map((item, i) => (
        <div key={`${i}-item`}>{item}</div>
      ))}
    </>
  );
}

你的代码是正确的,你只需要添加 logIndex,logs in use Effect bracket 如下所示,所以 addNextLogMessage 将引用新的状态。

每次更新状态时,都会创建一个新的 addNextLogMessage,如果您不使用新的更新间隔 (addNextLogMessage),它仍会引用第一个 logIndex 等于 0 的信息,因此它将始终显示“消息 1”

import React, { useState, useRef, useEffect } from "react";
import ReactDOM from "react-dom";

import "./styles.css";

function App() {
  const [logs, setLogs] = useState([]);
  const [timeOut, setTimeOut] = useState(2000);
  const interval = useRef(setTimeout(() => {}, 0));
  const [logIndex, setLogIndex] = useState(0);

  useEffect(() => {
    interval.current = setTimeout(addNextLogMessage, timeOut);
    return () => {
      clearTimeout(interval.current); // cleanup
    };
  }, [logIndex, logs]);

  const sourceLogs = [
    <p>Message 1</p>,
    <p>Message 2</p>,
    <p>Message 3</p>,
    <p>Message 4</p>,
    <p>Message 5</p>,
    <p>Message 6</p>
  ];
  
  const timeBetweenMessages = Math.ceil((1000 * 60 * 3) / sourceLogs.length);
  const addNextLogMessage = () => {
    setTimeOut(Math.random() * timeBetweenMessages + 500);
    setLogIndex((logIndex) => logIndex + 1);
    if (logIndex < sourceLogs.length) {
      setLogs((logs) => [
        ...logs,
        { ...sourceLogs[logIndex], timeStamp: new Date().toString() }
      ]);
    }
  };
  return <>{logs.map((item) => item)}</>;
}


const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);


测试一下here