React Native 倒计时组件 CountdownView 文档
一、组件简介
CountdownView
是一个功能灵活的 React Native 倒计时组件,支持天、小时、分钟、秒、毫秒多级时间显示,可自定义样式(字体颜色、背景渐变、尺寸等),并提供倒计时开始/结束的生命周期回调。适用于活动倒计时、秒杀场景、预约剩余时间展示等业务场景。
二、核心属性(Props)详解
属性名 | 类型 | 默认值 | 说明 |
---|---|---|---|
endTime |
number |
0 |
倒计时结束时间戳(单位:毫秒)。若传入秒级时间戳,需手动乘以 1000(组件内部会自动处理 >1e10 的情况)。 |
started |
boolean |
true |
是否启动倒计时(设为 false 可暂停)。 |
timeFontColor |
string |
'#000000' |
时间数字的字体颜色。 |
timeFontSize |
number |
14 |
时间数字的字体大小(单位:px)。 |
suffixText |
string |
':' |
时间单位后缀(如小时与分钟间的分隔符 : )。 |
suffixFontSize |
number |
14 |
后缀文本的字体大小(单位:px)。 |
isShowDay |
boolean |
true |
是否显示天数部分。 |
isShowHour |
boolean |
true |
是否显示小时部分。 |
isShowMinute |
boolean |
true |
是否显示分钟部分。 |
isShowSecond |
boolean |
true |
是否显示秒部分。 |
isShowMillisecond |
boolean |
false |
是否显示毫秒部分(开启后更新频率为 10ms)。 |
onStart |
() => void |
- |
倒计时开始时的回调函数。 |
onEnd |
() => void |
- |
倒计时结束(剩余时间 ≤0)时的回调函数。 |
style |
StyleProp<ViewStyle> |
- |
组件外层容器的自定义样式(如整体边距、对齐方式)。 |
timeTextBgStyle |
StyleProp<TextStyle> |
- |
时间数字背景的文本样式(仅对 LinearGradient 背景生效,用于调整内边距等)。 |
dayStartBgColor |
string |
transparent |
天数部分背景渐变的起始颜色(与 dayEndBgColor 配合使用)。 |
dayEndBgColor |
string |
transparent |
天数部分背景渐变的结束颜色。 |
dayFontColor |
string |
'#000000' |
天数数字的字体颜色(覆盖全局 timeFontColor )。 |
dayFontSize |
number |
14 |
天数数字的字体大小(覆盖全局 timeFontSize )。 |
dayPrefix |
string |
'' |
天数前缀(如 剩余 )。 |
daySuffix |
string |
'天' |
天数后缀(如 天 )。 |
dayPrefixStyle |
StyleProp<TextStyle> |
- |
天数前缀的自定义样式。 |
daySuffixStyle |
StyleProp<TextStyle> |
- |
天数后缀的自定义样式。 |
dayBgStyle |
StyleProp<ViewStyle> |
- |
天数背景容器的自定义样式(如圆角、内边距)。 |
hourStyle /minuteStyle /secondStyle /millisecondStyle |
StyleProp<TextStyle> |
- |
小时/分钟/秒/毫秒数字的自定义样式(覆盖全局 timeTextBgStyle )。 |
hourSuffixStyle 等后缀样式 |
StyleProp<TextStyle> |
- |
各时间单位后缀的自定义样式(覆盖全局 suffixStyle )。 |
三、使用示例
基础用法(默认显示所有时间单位)
tsx
import CountdownView from './CountdownView';
// 假设 24 小时后的时间戳(当前时间 + 24*3600*1000)
const endTime = Date.now() + 24 * 3600 * 1000;
const App = () => {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<CountdownView
endTime={endTime}
daySuffix="天"
suffixText=":"
/>
</View>
);
};
自定义样式(隐藏秒,调整背景渐变)
tsx
<CountdownView
endTime={endTime}
isShowSecond={false} // 隐藏秒
dayStartBgColor="#FF6B6B" // 天数背景渐变起始色
dayEndBgColor="#FFE66D" // 天数背景渐变结束色
hourStyle={{ fontWeight: 'bold' }} // 小时数字加粗
suffixStyle={{ color: '#666' }} // 后缀颜色变灰
timeFontSize={18} // 时间数字放大
/>
控制倒计时启停
tsx
const [isRunning, setIsRunning] = useState(true);
// 点击按钮暂停/恢复倒计时
const toggleCountdown = () => {
setIsRunning(!isRunning);
};
return (
<View>
<CountdownView
endTime={endTime}
started={isRunning}
onStart={() => console.log('倒计时开始')}
onEnd={() => console.log('倒计时结束!')}
/>
<Button
title={isRunning ? '暂停' : '恢复'}
onPress={toggleCountdown}
/>
</View>
);
四、源码
tsx
import React, {useEffect, useState} from 'react';
import {
StyleProp,
StyleSheet,
Text,
TextStyle,
View,
ViewStyle,
} from 'react-native';
// @ts-ignore
import LinearGradient from 'react-native-linear-gradient';
const CountdownView = ({
endTime = 0,
started = true,
timeFontColor = '#000000',
timeStartBgColor = 'transparent',
timeEndBgColor = 'transparent',
dayStartBgColor,
dayEndBgColor,
timeFontSize = 14,
suffixText = ':',
suffixHour,
suffixMinute,
suffixSecond,
suffixMillisecond,
suffixFontSize = 14,
dayFontColor = '#000000',
dayFontSize = 14,
dayPrefix = '',
daySuffix = '天',
isShowDay = true,
isShowHour = true,
isShowMinute = true,
isShowSecond = true,
isShowMillisecond = false,
style,
timeTextBgStyle,
suffixStyle,
dayPrefixStyle,
dayStyle,
dayBgStyle,
daySuffixStyle,
hourStyle,
hourSuffixStyle,
minuteStyle,
minuteSuffixStyle,
secondStyle,
secondSuffixStyle,
millisecondStyle,
millisecondSuffixStyle,
onStart,
onEnd,
updateTime,
}: {
endTime: number;
started?: boolean;
timeFontColor?: string;
timeStartBgColor?: string;
timeEndBgColor?: string;
dayStartBgColor?: string;
dayEndBgColor?: string;
timeFontSize?: number;
timeWidth?: number;
timeHeight?: number;
timeTextAlign?: 'top' | 'bottom' | 'center';
suffixText?: string;
suffixHour?: string;
suffixMinute?: string;
suffixSecond?: string;
suffixMillisecond?: string;
style?: StyleProp<ViewStyle>;
timeTextBgStyle?: StyleProp<TextStyle>;
timeBgStyle?: StyleProp<ViewStyle>;
timeSuffixTextStyle?: StyleProp<TextStyle>;
timeSuffixBgStyle?: StyleProp<ViewStyle>;
suffixFontSize?: number;
suffixWidth?: number;
suffixTextAlign?: 'top' | 'bottom' | 'center';
dayFontColor?: string;
dayFontSize?: number;
dayWidth?: number;
dayTextAlign?: 'top' | 'bottom' | 'center';
dayPrefix?: string;
daySuffix?: string;
isShowDay?: boolean;
isShowHour?: boolean;
isShowMinute?: boolean;
isShowSecond?: boolean;
isShowTimeSuffix?: boolean;
isShowTimePrefix?: boolean;
isShowMillisecond?: boolean;
dayPrefixStyle?: StyleProp<ViewStyle>;
dayStyle?: StyleProp<ViewStyle>;
suffixStyle?: StyleProp<TextStyle>;
dayBgStyle?: StyleProp<ViewStyle>;
daySuffixStyle?: StyleProp<ViewStyle>;
hourStyle?: StyleProp<ViewStyle>;
hourSuffixStyle?: StyleProp<ViewStyle>;
minuteStyle?: StyleProp<ViewStyle>;
minuteSuffixStyle?: StyleProp<ViewStyle>;
secondStyle?: StyleProp<ViewStyle>;
secondSuffixStyle?: StyleProp<ViewStyle>;
millisecondStyle?: StyleProp<ViewStyle>;
millisecondSuffixStyle?: StyleProp<ViewStyle>;
onStart?: () => void;
onEnd?: () => void;
updateTime?: (time: {
day: number;
hour: number;
minute: number;
second: number;
millisecond: number;
}) => void;
}) => {
const [state, setState] = useState({
day: 0,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
left: 0,
intervalId: null,
});
// 计算剩余时间
const calculateTimeLeft = () => {
const realEndTime = endTime > 1e10 ? endTime : endTime * 1000;
const left = realEndTime - Date.now();
return left <= 0
? {left: 0, ...getDefaultTime()}
: {
left,
day: Math.floor(left / (1000 * 24 * 60 * 60)),
hour: Math.floor((left % (1000 * 24 * 60 * 60)) / (1000 * 60 * 60)),
minute: Math.floor((left % (1000 * 60 * 60)) / (1000 * 60)),
second: Math.floor((left % (1000 * 60)) / 1000),
millisecond: left % 1000,
};
};
// 初始化时间状态
const getDefaultTime = () => ({
day: 0,
hour: 0,
minute: 0,
second: 0,
millisecond: 0,
});
// 启动倒计时
const startInterval = () => {
stopInterval();
onStart?.();
const intervalId = setInterval(
() => {
const {left, day, hour, minute, second, millisecond} =
calculateTimeLeft();
setState(prev => ({
...prev,
left,
day,
hour,
minute,
second,
millisecond,
}));
if (left <= 0) {
stopInterval();
onEnd?.();
}
},
isShowMillisecond ? 10 : 500,
);
// setState(prev => ({...prev, intervalId}));
};
// 停止倒计时
const stopInterval = () => {
if (state.intervalId) {
clearInterval(state.intervalId);
}
};
// 生命周期管理
useEffect(() => {
if (started) {
startInterval();
}
return () => stopInterval(); // 清理副作用
}, [started]);
// 格式化数字为两位
const formatNumber = (num, length = 2) =>
num.toString().padStart(length, '0');
// 构建单个时间单元
const renderTimeUnit = (value, suffix, show, aStyle, aSuffixStyle) => {
if (!show) return null;
return (
<View style={[styles.timeContainer]}>
<LinearGradient
style={timeTextBgStyle}
colors={[timeStartBgColor, timeEndBgColor]}
start={{x: 0, y: 0}}
end={{x: 1, y: 0}}>
<Text
style={[
styles.timeText,
{color: timeFontColor, fontSize: timeFontSize},
aStyle || {},
]}>
{formatNumber(value)}
</Text>
</LinearGradient>
{suffix && (
<Text
style={[
styles.suffixText,
{fontSize: suffixFontSize},
aSuffixStyle || {},
suffixStyle,
]}>
{suffix}
</Text>
)}
</View>
);
};
return (
<View
style={[
{
flexDirection: 'row',
alignItems: 'center',
},
style,
]}>
{isShowDay && (
<View style={styles.dayOut}>
<Text
style={[
styles.dayPrefix,
{fontSize: dayFontSize, color: dayFontColor},
dayPrefixStyle,
]}>
{dayPrefix}
</Text>
<LinearGradient
style={dayBgStyle}
colors={[dayStartBgColor, dayEndBgColor]}
start={{x: 0, y: 0}}
end={{x: 1, y: 0}}>
<Text
style={[
styles.dayValue,
{fontSize: dayFontSize, color: dayFontColor},
dayStyle,
]}>
{formatNumber(state.day)}
</Text>
</LinearGradient>
<Text
style={[
styles.daySuffix,
{fontSize: dayFontSize, color: dayFontColor},
daySuffixStyle,
]}>
{daySuffix}
</Text>
</View>
)}
{isShowHour &&
renderTimeUnit(
state.hour,
suffixHour ?? suffixText,
isShowHour,
hourStyle,
hourSuffixStyle,
)}
{isShowMinute &&
renderTimeUnit(
state.minute,
suffixMinute ?? suffixText,
isShowMinute,
minuteStyle,
minuteSuffixStyle,
)}
{isShowSecond &&
renderTimeUnit(
state.second,
suffixSecond,
isShowSecond,
secondStyle,
secondSuffixStyle,
)}
{isShowMillisecond &&
renderTimeUnit(
state.millisecond,
suffixMillisecond,
isShowMillisecond,
millisecondStyle,
millisecondSuffixStyle,
)}
</View>
);
};
// 样式定义
const styles = StyleSheet.create({
timeContainer: {
flexDirection: 'row',
alignItems: 'center',
},
timeText: {
textAlign: 'center',
},
suffixText: {
textAlign: 'center',
},
dayOut: {
flexDirection: 'row',
alignItems: 'center',
},
dayPrefix: {
textAlign: 'center',
},
dayValue: {
textAlign: 'center',
},
daySuffix: {
textAlign: 'center',
},
});
export default CountdownView;