繁体   English   中英

React Hooks state 变量在 setTimeout 期间未更新 function

[英]React Hooks state variable not updated during setTimeout function

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

很简单,对吧? 只需使用setTimout function。 不是用钩子!

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

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。有人有任何指针吗?

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

您的代码是正确的,您只需添加 logIndex,logs 使用效果括号,如下所示,因此 addNextLogMessage 将引用新的 state。

在每次更新 state 时,都会创建一个新的 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);


在这里测试

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM