场景重现
之前在做一个IM
的模块,有一个撤回消息后两分钟之内可以重新编辑
的功能,项目是用react
做的,按照正常的思路,只需传入一个消息撤回的时间markTime
,然后用现在时间Date.now()
去判断是否已经超时两分钟。然而理想总是美好的,由于没有去触发react
的重新渲染机制,即使超时了重新编辑
的按钮也不会消失,等到去点击重新编辑
按钮再提示已超时的话这个极大的影响用户体验。
解决方案
- 思路I
可以给每条撤回的系统消息加上一个setInterval
定时器轮询,只要未超时就实时更新当前时间,直至超时再把这个定时器清除掉;
但是这个方法有个弊端,就是假设我在有限时间内撤回了非常多条消息,那么这非常多条的消息就会有着对应的多个定时器在工作着,这对于性能的损耗特别的大,对于本公司的项目,团队在性能优化上还是有追求的,所以思路I被否决掉了;
- 思路II
通过静态方法维护同一个定时器订阅器,如果该订阅器中还有存在的未超过有限时间的事件,则展示其中未超过有限时间的子组件,如果已超时,则不展示其对应的自组件,返回一个null
。
上代码
/**
* 通过静态方法维护同一个定时器,传入标记时间markTime和在这段有效时长alidTimeLong内,2000毫秒更新一次组件,如果超过有效时长则返回null
*/
import React from "react";
type UpDateFunc = (now: number) => boolean;
// 使用该组件时,需要传入两个参数,一个开始定时的时间,一个有效时长,都为number类型
export default class TimePlay extends React.Component<{
markTime: number;
validTimeLong: number;
}> {
// 维护一个Set的订阅器
static countDownFuncList = new Set<UpDateFunc>();
// 定时器的初始状态
static intervalHandel = -1;
// 往订阅器中添加一个需要监听的事件
static addFunc = (func: UpDateFunc) => {
TimePlay.countDownFuncList.add(func);
TimePlay.countDown();
};
// 订阅器中删除一个超时的事件
static removeFunc = (func: UpDateFunc) => {
TimePlay.countDownFuncList.delete(func);
};
// 订阅器中如果还存在事件,则在同一个定时器下执行
static countDown = () => {
if (TimePlay.intervalHandel !== -1) {
return;
}
TimePlay.intervalHandel = setInterval(() => {
if (TimePlay.countDownFuncList.size === 0) {
clearInterval(TimePlay.intervalHandel);
TimePlay.intervalHandel = -1;
}
const now = Date.now();
for (const fn of TimePlay.countDownFuncList) {
const isValid = fn(now);
if (!isValid) {
TimePlay.removeFunc(fn);
}
}
}, 2000);
};
// 判断是否展示子组件
state = {
isShowChildren: this.props.markTime + this.props.validTimeLong > Date.now(),
};
// 用于初始时判断是否超时
updateState = (now: number) => {
const isValid = this.props.markTime + this.props.validTimeLong > now;
if (isValid && !this.state.isShowChildren) {
this.setState({ isShowChildren: true });
}
if (!isValid) {
this.setState({ isShowChildren: false });
}
return isValid;
};
componentDidMount() {
TimePlay.addFunc(this.updateState);
}
render() {
if (this.state.isShowChildren) {
return this.props.children;
} else {
return null;
}
}
}
通过此方法,如果在实际业务当中两分钟内撤回了很多条消息,依旧只需要静态方法中的一个定时器去轮询,而无需同时开启多个定时器。
当然,如果对render中的return结果进行改造,还可以应用于抢购或其他场景下同个页面有多个倒计时的场景。
如果你有更好的解决方案,欢迎讨论~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。