在 setInterval 内反应 setState
React setState within setInterval
我有一个存储对象数组的组件,例如:
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);
});
}
});
如果我只将项目一个接一个地添加到列表中(例如,计时器在添加新项目之前完成),这种有缺陷的方法可以正常工作。但是当同时添加多个项目时它就完全崩溃了:已经创建的第一个计时器没有考虑到后来添加的所有项目。
我知道这种方法一点也不理想,但是如果您有更好的方法来实现我想要的,我将不胜感激。谢谢
不要使用旧的 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];
});
});
}
});
您还需要确保在卸载或重新呈现组件时取消间隔计时器,因为您的代码在重新呈现时会启动 new 计时器(如果长度检查通过)。这些将很快叠加起来进行多次重叠更新。
你可以有 timer
return 一个 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; // ***
});
我有一个存储对象数组的组件,例如:
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);
});
}
});
如果我只将项目一个接一个地添加到列表中(例如,计时器在添加新项目之前完成),这种有缺陷的方法可以正常工作。但是当同时添加多个项目时它就完全崩溃了:已经创建的第一个计时器没有考虑到后来添加的所有项目。 我知道这种方法一点也不理想,但是如果您有更好的方法来实现我想要的,我将不胜感激。谢谢
不要使用旧的 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];
});
});
}
});
您还需要确保在卸载或重新呈现组件时取消间隔计时器,因为您的代码在重新呈现时会启动 new 计时器(如果长度检查通过)。这些将很快叠加起来进行多次重叠更新。
你可以有 timer
return 一个 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; // ***
});