CountDown
按格式展示剩余时间的倒计时组件
倒计时(CountDown)把一段毫秒时长渲染成可读的剩余时间。内部用 requestAnimationFrame 驱动,秒级模式下只在跨秒的那一帧才更新状态,空转的帧不会触发渲染;同时监听 AppState,从后台回到前台时按真实时钟校正剩余时间,而不是把挂起期间的时间"补跑"一遍。
import { CountDown } from '@skyroc/native-ui';基础用法
time 是总时长(毫秒),默认 format 为 HH:mm:ss,挂载后自动开始。
import { CountDown, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
const CountDownBasic = () => {
return (
<View className="gap-4 bg-background p-4">
<View className="gap-1">
<Text className="text-sm text-muted-foreground">60 秒倒计时</Text>
<CountDown time={60 * 1000} />
</View>
<View className="gap-1">
<Text className="text-sm text-muted-foreground">零时长</Text>
<CountDown
autoStart={false}
time={0}
/>
</View>
</View>
);
};
export { CountDownBasic };何时使用
- 展示距某个时刻还剩多久:秒杀、限时优惠、订单支付倒计时。
- 短时冷却:验证码重发、按钮防重复点击(见下方验证码场景)。
- 只需要一个静态时长文本、不随时间变化时,直接用
Text自己格式化即可。 - 需要显示"已过去多久"的正计时,本组件不支持——它只向 0 递减,到 0 自动停止。
自定义格式
format 支持以下占位符,其余字符原样输出:
| 占位符 | 含义 | 补零 |
|---|---|---|
DD | 天 | 2 位 |
HH | 小时 | 2 位 |
mm | 分钟 | 2 位 |
ss | 秒 | 2 位 |
SSS | 毫秒 | 3 位 |
SS | 百分秒 | 2 位 |
S | 十分秒 | 1 位 |
import { CountDown, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
const CountDownFormat = () => {
return (
<View className="gap-4 bg-background p-4">
<View className="gap-1">
<Text className="text-sm text-muted-foreground">完整日期时间</Text>
<CountDown
format="DD 天 HH 时 mm 分 ss 秒"
time={30 * 60 * 60 * 1000}
/>
</View>
<View className="gap-1">
<Text className="text-sm text-muted-foreground">高位累加到分钟</Text>
<CountDown
format="mm:ss"
time={90 * 1000}
/>
</View>
</View>
);
};
export { CountDownFormat };缺失的高位单位会累加到相邻的低位单位上:format 为 mm:ss 时天和小时都并入分钟,format 为 ss 时整段时长都用秒表示。这样任何单一单位的格式都能表示完整时长而不丢时间,代价是数值可能超出常规范围(90 秒不会显示成 01:30,就是 90)。
毫秒级
millisecond 打开后每帧都会更新状态(约 60 次/秒),只在确实需要毫秒精度时开启。不开启时 S / SS / SSS 仍能渲染,但只会在跨秒时刷新,看起来像是卡住。
import { CountDown, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
const CountDownMillisecond = () => {
return (
<View className="gap-4 bg-background p-4">
<View className="gap-1">
<Text className="text-sm text-muted-foreground">十分秒(S)</Text>
<CountDown
millisecond
format="ss:S"
time={10 * 1000}
/>
</View>
<View className="gap-1">
<Text className="text-sm text-muted-foreground">百分秒(SS)</Text>
<CountDown
millisecond
format="ss:SS"
time={10 * 1000}
/>
</View>
<View className="gap-1">
<Text className="text-sm text-muted-foreground">总毫秒数(SSS)</Text>
<CountDown
millisecond
format="SSS 毫秒"
time={10 * 1000}
/>
</View>
</View>
);
};
export { CountDownMillisecond };自定义样式
className 覆盖根容器,classNames 按 slot 细粒度覆盖,冲突时 className 优先级更高。
| slot | 作用位置 |
|---|---|
root | 根容器 View |
text | 默认渲染的时间文本,被 children 接管后不生效 |
import { CountDown, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
const CountDownStyles = () => {
return (
<View className="gap-4 bg-background p-4">
<View className="gap-1">
<Text className="text-sm text-muted-foreground">根容器 className</Text>
<CountDown
className="items-center rounded-xl bg-secondary py-3"
time={60 * 1000}
/>
</View>
<View className="gap-1">
<Text className="text-sm text-muted-foreground">root / text slot</Text>
<CountDown
classNames={{
root: 'items-center rounded-xl border border-primary py-3',
text: 'text-2xl font-semibold text-primary'
}}
time={60 * 1000}
/>
</View>
</View>
);
};
export { CountDownStyles };自定义渲染
children 是一个接收 CurrentTime 的渲染函数,用于把时间拆成独立的数字块。它只替换文本内容,根容器照常渲染,因此 className / classNames.root 依旧生效。
import { CountDown, Text } from '@skyroc/native-ui';
import { View } from 'react-native';
const CountDownCustomRender = () => {
return (
<View className="bg-background p-4">
<CountDown
className="flex-row items-center justify-center gap-2 rounded-xl bg-muted p-4"
time={60 * 60 * 1000}
>
{current => (
<>
<Text className="min-w-10 rounded-lg bg-primary px-2 py-1 text-center text-primary-foreground">
{current.hours}
</Text>
<Text className="text-primary">:</Text>
<Text className="min-w-10 rounded-lg bg-primary px-2 py-1 text-center text-primary-foreground">
{current.minutes}
</Text>
<Text className="text-primary">:</Text>
<Text className="min-w-10 rounded-lg bg-primary px-2 py-1 text-center text-primary-foreground">
{current.seconds}
</Text>
</>
)}
</CountDown>
</View>
);
};
export { CountDownCustomRender };children 接管后 format 与 classNames.text 都不再参与渲染,格式化完全由你自己决定。
手动控制
autoStart={false} 时挂载后不自动开始,交给 ref 驱动:
| 方法 | 说明 |
|---|---|
start() | 开始或从暂停处继续;已在计时时是空操作 |
pause() | 暂停,保留当前剩余时间 |
reset() | 回到 time 的时长 |
reset(totalTime) | 重置为指定时长(毫秒) |
import { Button, CountDown } from '@skyroc/native-ui';
import type { CountDownRef } from '@skyroc/native-ui';
import { useRef } from 'react';
import { View } from 'react-native';
const CountDownManual = () => {
const manualRef = useRef<CountDownRef>(null);
function handleReset() {
manualRef.current?.reset();
}
function handleResetTo(seconds: number) {
manualRef.current?.reset(seconds * 1000);
}
return (
<View className="bg-background p-4">
<View className="mb-4">
<CountDown
autoStart={false}
format="mm:ss"
ref={manualRef}
time={20 * 1000}
/>
</View>
<View className="flex-row flex-wrap items-center gap-3">
<Button
variant="tonal"
onPress={() => manualRef.current?.start()}
>
开始
</Button>
<Button
variant="tonal"
onPress={() => manualRef.current?.pause()}
>
暂停
</Button>
<Button
variant="outline"
onPress={handleReset}
>
重置
</Button>
<Button
variant="outline"
onPress={() => handleResetTo(5)}
>
重置为 5 秒
</Button>
</View>
</View>
);
};
export { CountDownManual };reset 之后是否自动重新开始由 autoStart 决定:autoStart 为 true 时重置即重新跑,为 false 时停在起点等 start()。
动态时长
计时过程中改变 time 会按新时长重新开始;改变时如果处于暂停状态,则停在新时长的起点。
import { Button, CountDown } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const DURATIONS = [10, 30, 60];
const CountDownDynamicTime = () => {
const [duration, setDuration] = useState(30);
return (
<View className="bg-background p-4">
<View className="mb-4">
<CountDown
format="mm:ss"
time={duration * 1000}
/>
</View>
<View className="flex-row flex-wrap items-center gap-3">
{DURATIONS.map(seconds => (
<Button
key={seconds}
variant={seconds === duration ? 'solid' : 'tonal'}
onPress={() => setDuration(seconds)}
>
{`${seconds} 秒`}
</Button>
))}
</View>
</View>
);
};
export { CountDownDynamicTime };结束回调
剩余时间归零时先停止计时再触发 onFinish,每轮只触发一次;前后台切换时会取消旧的动画帧链再重启,不会因为补发的回调而重复触发。onChange 则在每次更新时触发——毫秒模式下这意味着每帧一次,回调里不要做重活。
import { CountDown, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const CountDownFinish = () => {
const [seconds, setSeconds] = useState(5);
const [finishCount, setFinishCount] = useState(0);
return (
<View className="gap-2 bg-background p-4">
<View className="flex-row items-center gap-2">
<CountDown
format="ss"
time={5 * 1000}
onChange={current => setSeconds(current.seconds)}
onFinish={() => setFinishCount(prev => prev + 1)}
/>
<Text className="text-sm text-muted-foreground">onChange:剩余 {seconds} 秒</Text>
</View>
<Text className="text-sm text-muted-foreground">onFinish:已触发 {finishCount} 次</Text>
</View>
);
};
export { CountDownFinish };验证码场景
倒计时结束前禁用按钮,onFinish 里恢复可点,是最常见的用法。
import { Button, CountDown, Text } from '@skyroc/native-ui';
import { useState } from 'react';
import { View } from 'react-native';
const SMS_SECONDS = 60;
const CountDownSms = () => {
const [smsSending, setSmsSending] = useState(false);
return (
<View className="flex-row items-center gap-3 bg-background px-6 py-4">
<Button
disabled={smsSending}
variant="solid"
onPress={() => setSmsSending(true)}
>
{smsSending ? '重新发送' : '发送验证码'}
</Button>
{smsSending ? (
<CountDown
time={SMS_SECONDS * 1000}
onFinish={() => setSmsSending(false)}
>
{current => <Text color="muted">{current.seconds} 秒后可重发</Text>}
</CountDown>
) : null}
</View>
);
};
export { CountDownSms };API
CountDown
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| time | 倒计时总时长(毫秒),计时过程中改变会按新时长重新开始 | number | 0 |
| format | 时间格式,支持 DD / HH / mm / ss / S / SS / SSS | string | 'HH:mm:ss' |
| autoStart | 是否挂载后自动开始,同时决定 reset() 之后是否自动重新开始 | boolean | true |
| millisecond | 毫秒级渲染,开启后每帧更新(约 60 次/秒) | boolean | false |
| children | 自定义渲染函数,接收当前时间对象;根容器仍照常渲染 | (current: CurrentTime) => ReactNode | - |
| onChange | 每次更新时触发 | (current: CurrentTime) => void | - |
| onFinish | 倒计时归零时触发,每轮只触发一次 | () => void | - |
| className | 根容器类名 | string | - |
| classNames | 各 slot 的类名覆盖;children 接管渲染时 text slot 不再生效 | SlotClassNames<CountDownSlots> | - |
| ref | 命令式控制,暴露 start / pause / reset | Ref<CountDownRef> | - |
useCountDown
组件内部的计时逻辑同样对外导出,用于完全自定义渲染容器的场景:
import { useCountDown } from '@skyroc/native-ui';
const { current, pause, reset, start } = useCountDown({
autoStart: true,
millisecond: false,
onChange: current => {},
onFinish: () => {},
time: 60 * 1000
});入参与同名的组件属性含义一致(time 必填),返回值中的 current 是 CurrentTime,start / pause / reset 与 CountDownRef 的三个方法完全相同。
类型
import type { CountDownProps, CountDownRef, CountDownSlots, CurrentTime } from '@skyroc/native-ui';CountDownSlots
可通过 classNames 覆盖的 slot 名称。
SlotClassNames
classNames 的取值形态:把 slot 名映射到类名,每个 slot 都可选。本页用到的 slot 见 CountDownSlots。
CurrentTime
剩余时间对象,传给 children 与 onChange。各单位已按天/时/分/秒拆分,不随 format 变化。
| 字段 | 类型 | 说明 |
|---|---|---|
| days | number | 天数。 |
| hours | number | 小时(0-23)。 |
| minutes | number | 分钟(0-59)。 |
| seconds | number | 秒(0-59)。 |
| milliseconds | number | 毫秒(0-999)。 |
| total | number | 总剩余毫秒数。 |
CountDownRef
CountDown 暴露的命令式方法。
| 字段 | 类型 | 说明 |
|---|---|---|
| start | () => void | 开始或继续计时,已在计时时为空操作。 |
| pause | () => void | 暂停并保留当前剩余时间。 |
| reset | (totalTime?: number) => void | 重置为指定时长,缺省回到 time;autoStart 为 true 时自动重新开始。 |