React Hooks具有多个单独倒计时的警报

时间:2019-06-26 16:07:16

标签: javascript reactjs react-hooks

我一直在尝试构建一个具有多个警报的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的输入,我相信我能够朝正确的方向前进。我在代码中收到有关添加依赖项的进一步警告,但其他依赖项会导致我的代码出现错误。最终,我弄清楚了如何使用引用。我将其转换为自定义钩子。

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

3 个答案:

答案 0 :(得分:3)

您的代码有两件事,

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

答案 1 :(得分:0)

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

为清楚起见,请尝试在您的Alert组件内添加{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,使其不在每个渲染器上运行清理功能

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

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

答案 2 :(得分:0)

两个问题都回答了一些问题,所以经过一阵沮丧后,这才是我想到的方法:

  • 具有一个可管理“警报”数组的钩子
  • 每个“警报”组件都可以管理自己的破坏情况

但是,由于功能随每次渲染而改变,因此每次道具更改都会使计时器重置,至少可以这样说。

如果您要遵守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,然后通过在覆盖组件中设置“活动”状态属性来使esulint详尽的描述保持快乐,当将其更改为false时,要求自己销毁。