React挂钩带有单个倒计时的多个警报



我一直在试图构建一个用多个警报在设定的时间后消失的React应用程序。示例:https://codesandbox.io/s/multiple-alert-countdown-294lc

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function TimeoutAlert({ id, message, deleteAlert }) {
  const onClick = () => deleteAlert(id);
  useEffect(() => {
    const timer = setTimeout(onClick, 2000);
    return () => clearTimeout(timer);
  });
  return (
    <p>
      <button onClick={onClick}>
        {message} {id}
      </button>
    </p>
  );
}
let _ID = 0;
function App() {
  const [alerts, setAlerts] = useState([]);
  const addAlert = message => setAlerts([...alerts, { id: _ID++, message }]);
  const deleteAlert = id => setAlerts(alerts.filter(m => m.id !== id));
  console.log({ alerts });
  return (
    <div className="App">
      <button onClick={() => addAlert("test ")}>Add Alertz</button>
      <br />
      {alerts.map(m => (
        <TimeoutAlert key={m.id} {...m} deleteAlert={deleteAlert} />
      ))}
    </div>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

问题是,如果我创建多个警报,则它以不正确的顺序消失。例如,测试0,测试1,测试2应该从测试0,测试1等开始消失,而是测试1首先消失,测试0消失了。

我一直在看到对USEREFS的引用,但我的实现无法解决此错误。


有了 @ehab的输入,我相信我能够朝着正确的方向前进。我在代码中收到了有关添加依赖关系的进一步警告,但其他依赖项会导致我的代码行动。最终,我想出了如何使用Refs。我将其转换为自定义钩。

function useTimeout(callback, ms) {
  const savedCallBack = useRef();
  // Remember the latest callback
  useEffect(() => {
    savedCallBack.current = callback;
  }, [callback]);
  // Set up timeout
  useEffect(() => {
    if (ms !== 0) {
      const timer = setTimeout(savedCallBack.current, ms);
      return () => clearTimeout(timer);
    }
  }, [ms]);
}

您的代码有两个错误,

1(使用效果的方式意味着每次渲染组件时都会调用此功能,但是显然取决于用例,您希望将此功能调用一次,因此将其更改为

 useEffect(() => {
    const timer = setTimeout(onClick, 2000);
    return () => clearTimeout(timer);
  }, []);

将空数组添加为第二个参数,这意味着您的效果不取决于任何参数,因此仅应调用一次。

您的删除警报取决于创建功能时捕获的值,这是有问题的,因为当时您没有数组中的所有警报,请将其更改为

const deleteAlert =  id => setAlerts(alerts => alerts.filter(m => m.id !== id));

这是我分叉后工作的样本https://codesandbox.io/s/multiple-alert-countdown-02c2h

好吧,您的问题是您在每个重新渲染中重新安装,因此您基本上在渲染时重置了所有组件的计时器。

只是为了清楚一下,请尝试在警报组件中添加{Date.now()}

      <button onClick={onClick}>
        {message} {id} {Date.now()}
      </button>

您每次都会注意到重置

因此,要在功能组件中实现此目标,您需要使用React.memo

示例使您的代码工作我要做:

const TimeoutAlert = React.memo( ({ id, message, deleteAlert }) => {
  const onClick = () => deleteAlert(id);
  useEffect(() => {
    const timer = setTimeout(onClick, 2000);
    return () => clearTimeout(timer);
  });
  return (
    <p>
      <button onClick={onClick}>
        {message} {id}
      </button>
    </p>
  );
},(oldProps, newProps)=>oldProps.id === newProps.id) // memoization condition

第二个修复您的使用效应以不在每个渲染上运行清理功能

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

最后是关于口味的东西,但实际上您需要破坏{...m}对象吗?我会将其作为适当的道具通过,以避免每次创建新对象!

两个答案都错过了一些问题,所以经过一段时间的挫败感,这是我提出的方法:

  • 有一个管理"警报"数组的钩子
  • "警报"组件管理自己的破坏

但是,由于函数随着每个渲染的变化而发生变化,因此计时器将重置每个道具更改,至少可以说是不希望的。

,如果您试图尊重Eslint详尽的DEPS规则,它还增加了另一个复杂性,因为否则您将遇到国家响应能力的问题。其他建议,如果您要沿着使用" usecallback"的途径,那么您正在寻找错误的地方。

就我而言,我正在使用"覆盖层"。那段时间,但是您可以将它们想象为警报等。

打字稿:

// useOverlayManager.tsx
export default () => {
  const [overlays, setOverlays] = useState<IOverlay[]>([]);
  const addOverlay = (overlay: IOverlay) => setOverlays([...overlays, overlay]);
  const deleteOverlay = (id: number) =>
    setOverlays(overlays.filter((m) => m.id !== id));
  return { overlays, addOverlay, deleteOverlay };
};
// OverlayIItem.tsx
interface IOverlayItem {
  overlay: IOverlay;
  deleteOverlay(id: number): void;
}
export default (props: IOverlayItem) => {
  const { deleteOverlay, overlay } = props;
  const { id } = overlay;
  const [alive, setAlive] = useState(true);
  useEffect(() => {
    const timer = setTimeout(() => setAlive(false), 2000);
    return () => {
      clearTimeout(timer);
    };
  }, []);
  useEffect(() => {
    if (!alive) {
      deleteOverlay(id);
    }
  }, [alive, deleteOverlay, id]);
  return <Text>{id}</Text>;
};

然后在渲染组件的位置:

  const { addOverlay, deleteOverlay, overlays } = useOverlayManger();
  const [overlayInd, setOverlayInd] = useState(0);
  const addOverlayTest = () => {
    addOverlay({ id: overlayInd});
    setOverlayInd(overlayInd + 1);
  };

  return {overlays.map((overlay) => (
            <OverlayItem
              deleteOverlay={deleteOverlay}
              overlay={overlay}
              key={overlay.id}
            />
          ))};

基本上:每个覆盖层具有独特的ID。每个"覆盖"组件管理自己的破坏,覆盖层通过Prop功能将其传达给OverlayManger,然后通过设置" Alive Alive"来保持详尽的DEPS保持快乐。覆盖组件中的状态属性,当更改为false时,将要求其自己的破坏。

相关内容

  • 没有找到相关文章