Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/hooks/src/useCountDown/demo/demo4.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* title: Configure the server time
* desc: Because users may change their local system time, the countdown can become inaccurate when using targetDate.
*
* title.zh-CN: 配置服务器时间
* desc.zh-CN: 防止用户修改本地时间,导致配置 targetDate 时倒计时不准确,通过 currentServerTime 配置当前服务器时间
*/

import React, { useMemo } from "react";
import { useCountDown } from "ahooks";

const App: React.FC = () => {
// should be get from server
const currentServerTime = useMemo(() => Date.now(), []);

const [countdown] = useCountDown({ leftTime: 60 * 1000, currentServerTime });
return <p>{countdown}</p>;
};

export default App;
10 changes: 9 additions & 1 deletion packages/hooks/src/useCountDown/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ A hook for manage countdown.

<code src="./demo/demo3.tsx" />

## Config currentServerTime

<code src="./demo/demo4.tsx" />

## API

```typescript
Expand All @@ -37,6 +41,7 @@ const [countdown, formattedRes] = useCountDown(
leftTime,
targetDate,
interval,
currentServerTime,
onEnd
}
);
Expand All @@ -53,13 +58,16 @@ If you only need to be accurate to the second, you can use it like this `Math.ro

If both `leftTime` and `targetDate` are passed, the `targetDate` is ignored, the `leftTime` is dominant.

If 'currentServerTime' is not configured, the local time is used for the countdown.

### Params

| Property | Description | Type | Default |
| ---------- | -------------------------------------------- | ------------ | ------- |
| leftTime | The rest of time, in milliseconds | `number` | - |
| targetDate | Target time | `TDate` | - |
| interval | Time interval between ticks, in milliseconds | `number` | `1000` |
| currentServerTime | Current server time, in milliseconds | `number` | - |
| onEnd | Function to call when countdown completes | `() => void` | - |

### Return
Expand All @@ -71,4 +79,4 @@ If both `leftTime` and `targetDate` are passed, the `targetDate` is ignored, the

## Remark

`leftTime`、`targetDate`、`interval`、`onEnd` support dynamic change.
`leftTime`、`targetDate`、`interval`、`currentServerTime`、`onEnd` support dynamic change.
52 changes: 43 additions & 9 deletions packages/hooks/src/useCountDown/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface Options {
leftTime?: number;
targetDate?: TDate;
interval?: number;
currentServerTime?: number;
onEnd?: () => void;
}

Expand All @@ -20,12 +21,23 @@ export interface FormattedRes {
milliseconds: number;
}

const calcLeft = (target?: TDate) => {
const calcLeft = (
target?: TDate,
momentTimeInfo?: {
serverTime: number;
localTime: number;
}
) => {
if (!target) {
return 0;
}
// https://stackoverflow.com/questions/4310953/invalid-date-in-safari
const left = dayjs(target).valueOf() - Date.now();
const left =
// 如果服务器时间存在,则使用服务器时间,并动态计算时间差值,否则使用本地时间
dayjs(target).valueOf() -
(momentTimeInfo?.serverTime
? momentTimeInfo.serverTime + Date.now() - momentTimeInfo.localTime
: Date.now());
return left < 0 ? 0 : left;
};

Expand All @@ -40,15 +52,37 @@ const parseMs = (milliseconds: number): FormattedRes => {
};

const useCountdown = (options: Options = {}) => {
const { leftTime, targetDate, interval = 1000, onEnd } = options || {};
const {
leftTime,
targetDate,
interval = 1000,
currentServerTime,
onEnd,
} = options || {};

/** 缓存此刻时间,保存当前服务器时间和本地时间,用于动态计算时间差,单位ms */
const momentTimeInfo = useMemo(
() => ({
serverTime: currentServerTime || 0,
localTime: currentServerTime ? Date.now() : 0,
}),
[currentServerTime]
);

const memoLeftTime = useMemo<TDate>(() => {
return isNumber(leftTime) && leftTime > 0 ? Date.now() + leftTime : undefined;
}, [leftTime]);
return isNumber(leftTime) && leftTime > 0
? // 如果传入服务器时间,则使用服务器时间,并动态计算时间差值,否则使用本地时间
(momentTimeInfo.serverTime
? momentTimeInfo.serverTime + Date.now() - momentTimeInfo.localTime
: Date.now()) + leftTime
: undefined;
}, [leftTime, momentTimeInfo]);

const target = 'leftTime' in options ? memoLeftTime : targetDate;

const [timeLeft, setTimeLeft] = useState(() => calcLeft(target));
const [timeLeft, setTimeLeft] = useState(() =>
calcLeft(target, momentTimeInfo)
);

const onEndRef = useLatest(onEnd);

Expand All @@ -60,10 +94,10 @@ const useCountdown = (options: Options = {}) => {
}

// 立即执行一次
setTimeLeft(calcLeft(target));
setTimeLeft(calcLeft(target, momentTimeInfo));

const timer = setInterval(() => {
const targetLeft = calcLeft(target);
const targetLeft = calcLeft(target, momentTimeInfo);
setTimeLeft(targetLeft);
if (targetLeft === 0) {
clearInterval(timer);
Expand All @@ -72,7 +106,7 @@ const useCountdown = (options: Options = {}) => {
}, interval);

return () => clearInterval(timer);
}, [target, interval]);
}, [target, interval, momentTimeInfo]);

const formattedRes = useMemo(() => parseMs(timeLeft), [timeLeft]);

Expand Down
12 changes: 10 additions & 2 deletions packages/hooks/src/useCountDown/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ nav:

<code src="./demo/demo3.tsx" />

## 通过 currentServerTime 配置当前服务器时间

<code src="./demo/demo4.tsx" />

**说明**

useCountDown 的精度为毫秒,可能会造成以下几个问题
Expand All @@ -28,7 +32,9 @@ useCountDown 的精度为毫秒,可能会造成以下几个问题

如果你的精度只要到秒就好了,可以这样用 `Math.round(countdown / 1000)`。

如果同时传了 `leftTime` 和 `targetDate`,则会忽略 `targetDate`,以 `leftTime` 为主
如果同时传了 `leftTime` 和 `targetDate`,则会忽略 `targetDate`,以 `leftTime` 为主。

如果没有配置 `currentServerTime` ,则会使用本地时间进行倒计时 。

## API

Expand All @@ -48,6 +54,7 @@ const [countdown, formattedRes] = useCountDown(
leftTime,
targetDate,
interval,
currentServerTime,
onEnd
}
);
Expand All @@ -60,6 +67,7 @@ const [countdown, formattedRes] = useCountDown(
| leftTime | 剩余时间(毫秒) | `number` | - |
| targetDate | 目标时间 | `TDate` | - |
| interval | 变化时间间隔(毫秒) | `number` | `1000` |
| currentServerTime | 当前服务器时间(毫秒) | `number` | - |
| onEnd | 倒计时结束触发 | `() => void` | - |

### Result
Expand All @@ -71,4 +79,4 @@ const [countdown, formattedRes] = useCountDown(

## 备注

`leftTime`、`targetDate`、`interval`、`onEnd` 支持动态变化
`leftTime`、`targetDate`、`interval`、`currentServerTime`、`onEnd` 支持动态变化