【问题标题】:React: Mutating state through setTimeout return wrong resultsReact:通过 setTimeout 改变状态返回错误结果
【发布时间】:2019-02-19 10:21:33
【问题描述】:

我有一组警报作为我的状态。

我正在尝试让我的用户通过单击手动关闭警报,并在几秒钟后自动关闭它。

目前只有第三个警报会自动清除,其余的保持不变。

我什至可以通过当前的实现来实现我的目标吗? 如果不是,那么正确的探索方向是什么?

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

import "./styles.css";

function App() {
  const [alerts, setAlerts] = useState([
    { message: "Alert 1" },
    { message: "Alert 2" },
    { message: "Alert 3" },
    { message: "Alert 4" }
  ]);

  const dismissAlert = alertIndex => {
    setAlerts([
      ...alerts.slice(0, alertIndex),
      ...alerts.slice(alertIndex + 1)
    ]);
  };

  return (
    <div className="App">
      <Alerts alerts={alerts} dismissAlert={dismissAlert} />
    </div>
  );
}

const Alerts = ({ alerts, dismissAlert }) => (
  <div className="alerts">
    {alerts.map((alert, i) => (
      <Alert key={i} alert={alert} dismissAlert={() => dismissAlert(i)} />
    ))}
  </div>
);

const Alert = ({ alert, dismissAlert }) => {
  useEffect(() => {
    const timerId = setTimeout(dismissAlert, 1000);

    return () => {
      console.log("clearing timeout");
      clearTimeout(timerId);
    };
  }, []);

  return (
    <div className="alert" onClick={dismissAlert}>
      <p>{alert.message}</p>
    </div>
  );
};

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

Online SandBox

提前致谢。

【问题讨论】:

  • 您可能需要另一种方法来识别需要解除的警报,因为索引可能会在请求解除和解除实际发生的时间之间发生变化。
  • 显然 React 在更新 alerts 时没有清除我的 setTimeout,因为它得到了相同的 key(我想这就是为什么 eslint 总是抱怨索引作为键)更改 @ 987654327@ 到 i + alert.message 解决了我的问题。
  • 我认为它还有另一个错误。 4 条消息应同时清除。但它清楚的顺序
  • 是的,因为Alert 会重新渲染并在安装时获得一个新的setTimeout,我现在正尝试使用React.memo 解决这个问题。

标签: javascript reactjs


【解决方案1】:

检查您的代码的修改示例,我认为它或多或少地按照您的意图工作,但它可能会出现故障。我觉得效果不佳的事情是关闭 setTimeout 上的警报。似乎 setTimeout 正在删除可能已经删除的内容。因此,为了保持并发,我正在维护一个队列,该队列将保存要删除的项目。我也使用了id,而不是依赖可以在拼接后更改的索引。如果您需要进一步的帮助,请告诉我。

顺便说一句,您需要在setTimeouts 上进行锻炼,原因是在它们被渲染之后,所有这些都将被安排在一瞬间的差异内移除,这会让人感觉它们都被一起移除了

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

import "./styles.css";

function App() {
  useEffect(() => {
    setInterval(addAlert, 5000);
    setInterval(dismissAlert, 6000);
  }, []);

  const addAlert = () => {
    setAlerts([
      ...alerts,
      { id: new Date().getTime(), message: "new alert" + Math.random() }
    ]);
  };

  const [alerts, setAlerts] = useState([
    { id: 1, message: "Alert 1" },
    { id: 2, message: "Alert 2" },
    { id: 3, message: "Alert 3" },
    { id: 4, message: "Alert 4" }
  ]);

  const [alertsToRemove, addAlertsToRemove] = useState([]);

  const queueRemoval = alertId => {
    if (alertsToRemove.indexOf(alertId) <= -1) {
      alertsToRemove.push(alertId);
      addAlertsToRemove(alertsToRemove);
    }
  };

  const dismissAlert = () => {
    alertsToRemove.forEach(id => {
      var alertToDelete = alerts.find(a => a.id == id);
      alerts.splice(alerts.indexOf(alertToDelete));
      setAlerts(alerts);
    });
    addAlertsToRemove([]);
  };

  return (
    <div className="App">
      <Alerts alerts={alerts} queueRemoval={queueRemoval} />
    </div>
  );
}

const Alerts = ({ alerts, queueRemoval }) => (
  <div className="alerts">
    {alerts.map((alert, i) => (
      <Alert
        key={i}
        alert={alert}
        queueRemoval={() => queueRemoval(alert.id)}
      />
    ))}
  </div>
);

const Alert = ({ alert, queueRemoval }) => {
  useEffect(() => {
    const timerId = setTimeout(queueRemoval, 3000);

    return () => {
      console.log("clearing timeout");
      clearTimeout(timerId);
    };
  }, []);

  return (
    <div className="alert" onClick={queueRemoval}>
      <p>{alert.message}</p>
    </div>
  );
};

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

【讨论】:

    猜你喜欢
    • 2021-05-13
    • 2021-05-03
    • 1970-01-01
    • 2014-05-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-07
    • 2012-01-09
    相关资源
    最近更新 更多