功能介绍
用于管理倒计时的 Hooks。在日常工作中我们时常需要倒计时的帮助,但处理时间总是比较麻烦的事,而 useCountDown 可以帮助我们解决这类困难。
我们先思考一下实际的场景,假设我们要两天后的倒计时,那么我们要知道两天后距离现在的时间戳,然后通过对应的时间戳转换为对应的 天、时、分、秒,完成倒计时。
可看出,要想计算倒计时,就得具备两个条件:
- targetDate:目标时间,如:上述示例的两天后;
- interval:变化的时间,通常为 1s === 1000 ms。
返参只要返回目标时间距离当前时间的时间戳(remainTime
),和转化后的天、时、分等(formattedTime
)即可。
useCountDown 的 targetDate 可能存在多种形式,比如字符串、数字、日期等格式,转化起来相对麻烦,所以我们这里直接用 dayjs 库,来帮助我们解决这个问题。
目标时间与当前时间的时间差:
时间戳进行转化:
时间变化的条件:targetDate、interval。 每秒都会变化,所以这里我们依靠 setInterval 的即可。
targetTime 和 onEnd
useCountDown 除了上述功能外,我们可以扩展些额外的功能,让 useCountDown 更加完美,如:
- targetTime: 剩余时间,当前时间 + 剩余时间 = 目标时间;
- onEnd: 当倒计时结束后,触发回调函数。
可以看出 targetTime 相当于 targetDate 是个简化的版本,所以我们只需要做个兼容即可:
而 onEnd 触发的时机为 remain === 0 时即可。
单元测试
日期测试
在 useCountDown 的单元测试中,涉及到了时间的测试,在这里,我们需要 jest.useFakeTimers 的帮助。
jest.useFakeTimers:模拟假计时器,当我们需要日期、性能、时间、计时器的功能,如:Date、setTimeout()、clearTimeout()、setInterval()、clearInterval() 等,都可以通过它来实现。
但在 Jest 之前的版本中,jest.useFakeTimers 的使用比较麻烦,但在 Jest 26 中,加入 modern 方式来激活定时器的配置(参考文档),如:
但调用 jest.useFakeTimers 会对文件中的所有测试使用假计时器,这种行为是一个全局操作,会影响同一文件的其他测试。
所以,在使用 jest.useFakeTimers 的时候必须配合使用 jest.useRealTimers,它的作用是恢复全局日期、性能、时间和计时器 API 的原始实现。
定时器测试
当我们设置好环境后,还需要掌握 Jest 中测试定时器的方法:jest.advanceTimersByTime(msToRun),它可以执行宏任务队列。
换句话说,我们在开发中使用的 setTimeout() 、setInterval()、setImmediate() 都可通过 jest.advanceTimersByTime 进行对应的模拟操作。
测试用例:
简要地说明下:首先我们通过 renderHook 设置 useCountDowen 的剩余时间还剩 3s,然后执行 jest.advanceTimersByTime(1000)
模拟定时器执行一秒,所以此时剩余的时间还剩 2s。
注意❗
注意:jest. advanceTimersByTime 模拟是定时器的操作,所以它依然要放入 act 中才会有效果。
设置系统时间
在 useCountDown 的设计中,有可能目标的时间小于当前时间,此时返回的应该为 0,但对应的测试中,我们想要自由地去设置当前的时间,这种情况下就可以使用 jest.setSystemTime
的帮助。
jest.setSystemTime(now?: number | Date): 模拟程序中运行时的系统时钟,会影响当前时间,但它本身不会触发定时器等。
举个小例子:
在测试 targetDate 小于当前时间时,我们给的目标时间是 2021-01-01,是小于 2023 年的,但我们通过 jest.setSystemTime 更改后变为了 2020-01-01,此时测试的时间就会大于当前时间,也就是时间戳并不为 0。如:
所以我们要想测试这个用例,比 2020-01-01 小就 OK 了,当然,如果不设置 jest.setSystemTime 会默认为当前时间。