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

时间:2021-02-02 11:57:19

标签: javascript reactjs react-hooks settimeout

我有一个页面正在使用从数组中提取的日志消息更新模拟“控制台”。它应该工作的方式是每 n 秒一条新消息被添加到日志中。

很简单吧?只需使用 number 函数。不使用 Hooks!

这是一些(删节)代码:

setTimout

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

2 个答案:

答案 0 :(得分:1)

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

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>
      ))}
    </>
  );
}

答案 1 :(得分:1)

你的代码是正确的,你只需要添加 logIndex,logs in use Effect 如下所示,所以 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