我一直在尝试构建一个具有多个警报的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]);
}
答案 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时,要求自己销毁。