我一直在试图构建一个用多个警报在设定的时间后消失的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时,将要求其自己的破坏。