前言
ps: 本文使用ant组件库和react技术栈,因此假定你导入了这些依赖包。
如果让你实现一个显示当前日期的定时器组件,你会怎么做?如下图所示:
初步实现探索
碰到这样的需求,你是不是会使用定时器来实现,如下所示:
tsx
import React, { useEffect, useState } from 'react';
import { Statistic } from 'antd';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
dayjs.extend(utc);
const Interval = ({ utcOffset, style = {} }) => {
const [currentTime, setCurrentTime] = useState(
dayjs().utcOffset(utcOffset).format('YYYY 年 MM 月 DD 日 HH : mm : ss')
);
useEffect(() => {
const interval = setInterval(() => {
setCurrentTime(
dayjs().utcOffset(utcOffset).format('YYYY 年 MM 月 DD 日 HH : mm : ss')
);
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<div style={{ ...style, display: 'flex', justifyContent: 'center' }}>
<Statistic value={currentTime} valueStyle={{ fontSize: '18px' }} />
<span
style={{
fontSize: '18px',
color:'rgb(203,207,212)',
paddingLeft:'20px'
}}
>UTC{utcOffset}</span>
</div>
);
};
export default Interval;
可以看到,我们使用到了useEffect,在里面使用setInterval方法来不停的设置值,从而达到实现定时器组件,但其实我们没有必要这样去实现。
那我们应该如何去实现这样一个组件呢,假如项目当中引入了ahooks,也许还会有人想我们可以使用hooks的useInterval或者自己封装实现一个useInterval方法来替代这里的setInterval。如下所示:
tsx
import React, { useEffect, useState } from 'react';
import { Statistic } from 'antd';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import { useInterval } from 'ahooks';
dayjs.extend(utc);
const Interval = ({ utcOffset, style = {} }) => {
const [currentTime, setCurrentTime] = useState(
dayjs().utcOffset(utcOffset).format('YYYY 年 MM 月 DD 日 HH : mm : ss')
);
useInterval(() => {
setCurrentTime(
dayjs().utcOffset(utcOffset).format('YYYY 年 MM 月 DD 日 HH : mm : ss')
);
}, 1000);
return (
<div style={{ ...style, display: 'flex', justifyContent: 'center' }}>
<Statistic value={currentTime} valueStyle={{ fontSize: '18px' }} />
<span
style={{
fontSize: '18px',
color:'rgb(203,207,212)',
paddingLeft:'20px'
}}
>UTC{utcOffset}</span>
</div>
);
};
export default Interval;
这个组件我们还可以优化,比如去掉div标签和span标签,不过最终我们实现的方式都是通过一个定时器(要么借助hooks的定时器,要么自己实现定时器)来实现。
但其实我们并不需要这么实现,我们可以基于antd的倒计时组件来实现,下面我们就来一起看下吧。
更方便的实现
也许有人会疑惑,为什么antd的倒计时组件可以实现,那我就要反问一个问题,定时器组件的核心是什么?是定时器对不对?也就是不停的执行,倒计时在限定的时间内不停的执行不就可以看作是一个定时器吗?只不过这个定时器有结束的时候。
因此,我们要想基于倒计时组件来实现,那么首先就需要实现一个没有结束的倒计时,然后基于这个没有结束的倒计时去自定义倒计时的显示值。因此核心实现就2点,总结如下:
- 实现一个没有结束的倒计时。
- 自定义倒计时组件的显示值。
实现一个没有结束的倒计时(即定时器)
那么我们如何实现一个没有结束的倒计时呢?那么我们就要去了解antd倒计时组件的api,通过api我们可以看到倒计时组件提供一个value属性,如果value属性存在一个差值,那么倒计时就会循环执行。例如:
tsx
// 倒计时差值
const diffValue = Date.now() + 1000;
// 组件渲染
<Countdown value={diffValue} />
倒计时就会有1s的差值,然后倒计时组件就会有1s的倒计时,在这1s期间就是不停的执行,然后倒计时结束就会执行onFinish方法。
到了这里,也许有读者就会知道了,没错,我们就是定义一个状态用来管理这个差值,然后在倒计时结束的时候初始化这个差值。如下所示:
tsx
const [diffValue, setDiffValue] = useState(Date.now() + 1000);
const resetCountdown = () => {
setDiffValue(Date.now() + 1000);
};
const onFinish = () => {
resetCountdown();
};
// 组件渲染
<Countdown
onFinish={onFinish}
value={diffValue}
/>
你以为到了这里就完了吗?虽然我们更改了状态,但是组件并没有更新,因此视图永远都会停留在当前日期,不会跟随着递增,因此这里我们就需要时刻更新这个组件,没错,就是给组件添加一个key值。如下所示:
tsx
// ...
<Countdown
onFinish={onFinish}
value={diffValue}
key={diffValue}
/>
如此一来,我们的定时器组件就会随着diffValue的变动而不断更新,从而达到定时器效果,第一步我们就完成了。
自定义倒计时的展示值
接下来,我们就需要自定义实现倒计时的展示,通过倒计时组件的api,我们知道有两种方式来自定义展示值。
其一,我们可以通过format来自定义展示值,不过这个展示仅仅只能格式化日期格式,并不能自定义标签什么的展示。
其二,通过观察倒计时组件源码,我们可以发现倒计时组件是基于Statistic组件来渲染的,因此我们也可以通过formatter来自定义展示值。
这里我们仅仅只是自定义展示值(根据时区来自定义展示日期时间),因此,我们采用第一种方式来实现。代码如下所示:
tsx
const getValue = () =>
utc ? dayjs().utcOffset(utc).format('YYYY 年 MM 月 DD 日 HH : mm : ss') : dayjs().format('YYYY年MM月DD日 HH:mm:ss');
// 组件展示
<Countdown
key={count}
onFinish={onFinish}
value={count}
format={getValue()}
/>
如此一来,我们就实现了一个定时器展示日期,接下来我们还需要展示时区,我们只需要指定suffix属性即可,如下所示:
tsx
<Countdown
key={count}
onFinish={onFinish}
value={count}
format={getValue()}
// 自定义时区
suffix={utc && <span style={{ marginLeft: 5 }}>{`UTC(${utc})`}</span>}
/>
接下来,我们再完善封装一下,一个定时器组件就大功告成了,如下所示:
tsx
import { Statistic } from 'antd';
import { CountdownProps } from 'antd/lib/statistic/Countdown';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import React, { useState } from 'react';
const { Countdown } = Statistic;
dayjs.extend(utc);
export type AntdIntervalProps = {
countDownProps: CountdownProps;
utc: string;
};
const AntdInterval: React.FC<Partial<AntdIntervalProps>> = (props) => {
const { countDownProps = {}, utc = '+08:00' } = props;
const getValue = () =>
utc
? dayjs().utcOffset(utc).format('YYYY 年 MM 月 DD 日 HH : mm : ss')
: dayjs().format('YYYY年MM月DD日 HH:mm:ss');
const [diffValue, setDiffValue] = useState(Date.now() + 1000);
const resetCountdown = () => {
setDiffValue(Date.now() + 1000);
};
const onFinish = () => {
resetCountdown();
};
return (
<Countdown
key={diffValue}
onFinish={onFinish}
value={diffValue}
format={getValue()}
suffix={utc && <span style={{ marginLeft: 5 }}>{`UTC(${utc})`}</span>}
{...countDownProps}
/>
);
};
export default AntdInterval;
可以看到我们设计了countDownProps和utc两个props,countDownProps也就是antd倒计时组件的props,而utc就是我们的时区。以下是一个最终实现版本的在线示例。(后面再补上掘金代码段地址)
基于acro design的实现
acro design倒计时组件的api与antd有点差异,不过实现原理都是大同小异的,如下所示:
tsx
import { Statistic, StatisticProps, CountdownProps } from '@arco-design/web-react';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc';
import React, { useState } from 'react';
const { Countdown } = Statistic;
dayjs.extend(utc);
export type IntervalProps = {
countDownProps: CountdownProps;
StatisticProps: StatisticProps;
utc: string;
};
const Interval: React.FC<Partial<IntervalProps>> = props => {
const { countDownProps = {}, StatisticProps = {}, utc = '+08:00' } = props;
const getValue = () =>
utc ? dayjs().utcOffset(utc).format('YYYY 年 MM 月 DD 日 HH : mm : ss') : dayjs().format('YYYY年MM月DD日 HH:mm:ss');
const [count, setCount] = useState(Date.now() + 1000);
const resetCountdown = () => {
setCount(Date.now() + 1000);
};
const onFinish = () => {
resetCountdown();
};
return (
<Countdown
key={count}
onFinish={onFinish}
value={count}
renderFormat={() => <Statistic value={getValue()} suffix={utc && `UTC(${utc})`} {...StatisticProps} />}
{...countDownProps}
/>
);
};
export default Interval;
其它react ui组件库的实现也是同理,只要有倒计时组件就可以实现。
总结
读完本文,如果你还在采用最开始提到的实现方式来实现一个定时器组件,那就没有必要了,赶紧换成最后这种方式吧。