reactjs - 在 setInterval 中反应 setState
问题描述
我有一个组件,其中存储了一个对象数组,例如:
const [items, setItems] = React.useState([]);
我可以通过单击一个按钮向该数组添加一些元素:
const addItem = () => {
setItems(items => [ ...items, { value: 'foo', color: 'yellow' }]);
};
现在,我想让列表中的每个项目每 500 毫秒从黄色闪烁到红色,持续 5 秒。所以我创建了一个通用计时器函数来帮助我,例如:
const timer = (timeout: number, interval: number, callback: (n: number) => void) => {
const start = new Date().getTime();
let n = 0;
const _timer = setInterval(() => {
if ((new Date().getTime() - start) >= timeout) {
clearInterval(_timer);
} else {
callback(++n);
}
}, interval);
};
我通过在我的组件中执行以下操作来触发该计时器:
React.useEffect(() => {
// I also store the previous state, so that I trigger a timer only when new items
// are added to my array. Yeah not ideal...
if (items.length > previousItems.length) {
const lastItem = items.length - 1;
timer(5000, 500, (n: number) => {
const updatedItems = [ ...items ];
updatedItems[index].color = n % 2 == 0 ? 'yellow' : 'red';
setItems(items => updatedItems);
});
}
});
如果我只将项目一个一个地添加到列表中(例如,计时器在添加新项目之前完成),这种有缺陷的方法可以正常工作。但是同时添加多个item的时候就彻底坏了:第一个定时器已经创建并没有考虑到后面添加的所有item。我知道这种方法根本不理想,但是如果您有更好的方法来实现我想要的,我将不胜感激。谢谢。
解决方案
不要使用 old items
,而是使用你的 setter 回调传递的那个,请参阅***
评论:
React.useEffect(() => {
if (items.length > previousItems.length) {
const lastItem = items.length - 1;
timer(5000, 500, (n: number) => {
// *** Move the entire update into the callback
setItems(items => {
const updatedItems = [ ...items ]; // <=== Now this is using the up-to-date `items`
updatedItems[index].color = n % 2 == 0 ? 'yellow' : 'red';
return [updatedItems];
});
});
}
});
您还需要确保在组件卸载或重新渲染时取消间隔计时器,因为您的代码在重新渲染时启动一个新计时器(如果长度检查通过)。这些将很快叠加起来进行多次重叠更新。
你可以timer
返回一个cancel
函数:
const timer = (timeout: number, interval: number, callback: (n: number) => void) => {
const start = new Date().getTime();
let n = 0;
const _timer = setInterval(() => {
if ((new Date().getTime() - start) >= timeout) {
clearInterval(_timer);
} else {
callback(++n);
}
}, interval);
return () => clearInterval(_timer); // ***
};
然后将其用作useEffect
清理回调:
React.useEffect(() => {
if (items.length <= previousItems.length) {
return;
}
const lastItem = items.length - 1;
const cancel = timer(5000, 500, (n: number) => {
// ^^^^^^^^^^^^
// *** Move the entire update into the callback
setItems(items => {
const updatedItems = [ ...items ]; // <=== Now this is using the up-to-date `items`
updatedItems[index].color = n % 2 == 0 ? 'yellow' : 'red';
return [updatedItems];
});
});
return cancel; // ***
});
推荐阅读
- tsql - 展平递归数据
- node.js - 私人图像托管 (~50MB) 在登录后显示在网站上
- javascript - 试图向对象添加属性,但它删除了所有以前的而不是添加到它
- mysql - 如何在 PhpMyAdmin GUI 中设置主从复制
- windows - 我正在使用 Pywinauto GUI 自动化并在 Adobe Acrobat 菜单或其子菜单上使用控件时遇到错误
- javascript - 如何访问从父组件中的一个 Vue 组件存储的 vanilla 类实例?
- javascript - 根据键从数组中删除项目
- php - 登录后如何跳转到上一页?
- php - PHP 文件未捕获 AJAX 响应
- google-app-engine - 无法通过 IAP 授权访问 App Engine