
一、核心原理:计时器的设计与实现
1.1 计时器的设计理念
计时器是一个实用的时间管理工具,主要用于:
- 倒计时:设置特定时间后进行提醒
- 秒表:记录时间间隔
- 定时提醒:在特定时间点提醒用户
- 时间追踪:记录任务耗时
1.2 计时器的核心要素
一个完整的计时器需要考虑:
- 时间显示:显示小时、分钟、秒
- 时间设置:允许用户设置时间
- 开始/暂停:控制计时器的运行状态
- 重置功能:将计时器重置到初始状态
- 完成提醒:时间到达时的提醒
- 状态管理:管理计时器的运行状态
- 格式化显示:将秒数格式化为 HH:MM:SS 格式
1.3 实现原理
计时器的核心实现原理:
- 使用 useState 管理剩余时间
- 使用 useEffect 和 setInterval 实现倒计时
- 使用 useRef 存储定时器引用
- 将秒数格式化为 HH:MM:SS 格式
- 使用 TouchableOpacity 实现按钮交互
- 使用 ScrollView 确保页面可滚动
二、基础计时器实现
2.1 组件结构
计时器组件包含以下部分:
- 时间显示:显示剩余时间
- 时间设置:设置小时、分钟、秒
- 控制按钮:开始、暂停、重置按钮
- 进度条:显示剩余时间的进度
2.2 完整代码实现
javascript
import React, { useState, useEffect, useRef, memo, useCallback } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
SafeAreaView,
ScrollView,
} from 'react-native';
// 计时器组件
const Timer = memo(() => {
const [totalSeconds, setTotalSeconds] = useState(300); // 总时间(秒)
const [remainingSeconds, setRemainingSeconds] = useState(300); // 剩余时间(秒)
const [isRunning, setIsRunning] = useState(false); // 是否运行中
const [hours, setHours] = useState(0); // 设置的小时
const [minutes, setMinutes] = useState(5); // 设置的分钟
const [seconds, setSeconds] = useState(0); // 设置的秒
const timerRef = useRef<NodeJS.Timeout | null>(null);
// 格式化时间显示
const formatTime = useCallback((totalSeconds: number): string => {
const h = Math.floor(totalSeconds / 3600);
const m = Math.floor((totalSeconds % 3600) / 60);
const s = totalSeconds % 60;
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
}, []);
// 开始计时
const startTimer = useCallback(() => {
if (isRunning || remainingSeconds <= 0) return;
setIsRunning(true);
}, [isRunning, remainingSeconds]);
// 暂停计时
const pauseTimer = useCallback(() => {
setIsRunning(false);
}, []);
// 重置计时
const resetTimer = useCallback(() => {
setIsRunning(false);
const newTotal = hours * 3600 + minutes * 60 + seconds;
setTotalSeconds(newTotal);
setRemainingSeconds(newTotal);
}, [hours, minutes, seconds]);
// 应用设置的时间
const applySettings = useCallback(() => {
const newTotal = hours * 3600 + minutes * 60 + seconds;
if (newTotal > 0) {
setIsRunning(false);
setTotalSeconds(newTotal);
setRemainingSeconds(newTotal);
}
}, [hours, minutes, seconds]);
// 倒计时逻辑
useEffect(() => {
if (isRunning && remainingSeconds > 0) {
timerRef.current = setInterval(() => {
setRemainingSeconds(prev => {
if (prev <= 1) {
setIsRunning(false);
return 0;
}
return prev - 1;
});
}, 1000);
} else {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
}
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
}, [isRunning, remainingSeconds]);
// 计算进度百分比
const progress = totalSeconds > 0 ? ((totalSeconds - remainingSeconds) / totalSeconds) * 100 : 0;
return (
<SafeAreaView style={styles.container}>
<ScrollView style={styles.scrollView}>
{/* 标题区域 */}
<View style={styles.header}>
<Text style={styles.title}>React Native for Harmony</Text>
<Text style={styles.subtitle}>计时器工具</Text>
</View>
{/* 计时器主体 */}
<View style={styles.timerContainer}>
{/* 时间显示 */}
<View style={styles.timeDisplay}>
<Text style={styles.timeText}>{formatTime(remainingSeconds)}</Text>
</View>
{/* 进度条 */}
<View style={styles.progressContainer}>
<View style={styles.progressBar}>
<View style={[styles.progressFill, { width: `${progress}%` }]} />
</View>
</View>
{/* 控制按钮 */}
<View style={styles.controlButtons}>
<TouchableOpacity
style={[styles.controlButton, isRunning ? styles.pauseButton : styles.startButton]}
onPress={isRunning ? pauseTimer : startTimer}
disabled={remainingSeconds <= 0}
>
<Text style={styles.controlButtonText}>
{isRunning ? '暂停' : '开始'}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.controlButton}
onPress={resetTimer}
>
<Text style={styles.controlButtonText}>重置</Text>
</TouchableOpacity>
</View>
{/* 时间设置 */}
<View style={styles.settingsContainer}>
<Text style={styles.settingsTitle}>设置时间</Text>
<View style={styles.timeInputs}>
<View style={styles.timeInput}>
<Text style={styles.timeInputLabel}>小时</Text>
<TouchableOpacity
style={styles.timeInputButton}
onPress={() => setHours(prev => Math.min(prev + 1, 23))}
>
<Text style={styles.timeInputButtonText}>+</Text>
</TouchableOpacity>
<Text style={styles.timeInputValue}>{hours}</Text>
<TouchableOpacity
style={styles.timeInputButton}
onPress={() => setHours(prev => Math.max(prev - 1, 0))}
>
<Text style={styles.timeInputButtonText}>-</Text>
</TouchableOpacity>
</View>
<View style={styles.timeInput}>
<Text style={styles.timeInputLabel}>分钟</Text>
<TouchableOpacity
style={styles.timeInputButton}
onPress={() => setMinutes(prev => Math.min(prev + 1, 59))}
>
<Text style={styles.timeInputButtonText}>+</Text>
</TouchableOpacity>
<Text style={styles.timeInputValue}>{minutes}</Text>
<TouchableOpacity
style={styles.timeInputButton}
onPress={() => setMinutes(prev => Math.max(prev - 1, 0))}
>
<Text style={styles.timeInputButtonText}>-</Text>
</TouchableOpacity>
</View>
<View style={styles.timeInput}>
<Text style={styles.timeInputLabel}>秒</Text>
<TouchableOpacity
style={styles.timeInputButton}
onPress={() => setSeconds(prev => Math.min(prev + 1, 59))}
>
<Text style={styles.timeInputButtonText}>+</Text>
</TouchableOpacity>
<Text style={styles.timeInputValue}>{seconds}</Text>
<TouchableOpacity
style={styles.timeInputButton}
onPress={() => setSeconds(prev => Math.max(prev - 1, 0))}
>
<Text style={styles.timeInputButtonText}>-</Text>
</TouchableOpacity>
</View>
</View>
<TouchableOpacity
style={styles.applyButton}
onPress={applySettings}
>
<Text style={styles.applyButtonText}>应用设置</Text>
</TouchableOpacity>
</View>
</View>
{/* 说明区域 */}
<View style={styles.infoCard}>
<Text style={styles.infoTitle}>💡 功能说明</Text>
<Text style={styles.infoText}>• 倒计时:设置时间后自动倒计时</Text>
<Text style={styles.infoText}>• 暂停/继续:随时暂停和恢复计时</Text>
<Text style={styles.infoText}>• 重置功能:一键重置到初始时间</Text>
<Text style={styles.infoText}>• 进度显示:实时显示剩余时间进度</Text>
<Text style={styles.infoText}>• 自定义时间:支持设置小时、分钟、秒</Text>
<Text style={styles.infoText}>• 鸿蒙端完美兼容,运行稳定</Text>
</View>
</ScrollView>
</SafeAreaView>
);
});
Timer.displayName = 'Timer';
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5F7FA',
},
scrollView: {
flex: 1,
},
// ======== 标题区域 ========
header: {
padding: 20,
backgroundColor: '#FFFFFF',
borderBottomWidth: 1,
borderBottomColor: '#EBEEF5',
},
title: {
fontSize: 24,
fontWeight: '700',
color: '#303133',
textAlign: 'center',
marginBottom: 8,
},
subtitle: {
fontSize: 16,
fontWeight: '500',
color: '#909399',
textAlign: 'center',
},
// ======== 计时器容器 ========
timerContainer: {
backgroundColor: '#FFFFFF',
margin: 16,
borderRadius: 16,
padding: 20,
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 8,
elevation: 4,
},
// ======== 时间显示 ========
timeDisplay: {
backgroundColor: '#F5F7FA',
borderRadius: 12,
padding: 30,
alignItems: 'center',
marginBottom: 20,
},
timeText: {
fontSize: 48,
fontWeight: '700',
color: '#303133',
fontFamily: 'monospace',
},
// ======== 进度条 ========
progressContainer: {
marginBottom: 20,
},
progressBar: {
height: 8,
backgroundColor: '#EBEEF5',
borderRadius: 4,
overflow: 'hidden',
},
progressFill: {
height: '100%',
backgroundColor: '#409EFF',
borderRadius: 4,
},
// ======== 控制按钮 ========
controlButtons: {
flexDirection: 'row',
justifyContent: 'center',
marginBottom: 30,
},
controlButton: {
backgroundColor: '#F5F7FA',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
marginHorizontal: 8,
},
startButton: {
backgroundColor: '#67C23A',
},
pauseButton: {
backgroundColor: '#E6A23C',
},
controlButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#303133',
},
// ======== 时间设置 ========
settingsContainer: {
borderTopWidth: 1,
borderTopColor: '#EBEEF5',
paddingTop: 20,
},
settingsTitle: {
fontSize: 18,
fontWeight: '600',
color: '#303133',
marginBottom: 16,
textAlign: 'center',
},
timeInputs: {
flexDirection: 'row',
justifyContent: 'space-around',
marginBottom: 20,
},
timeInput: {
alignItems: 'center',
},
timeInputLabel: {
fontSize: 14,
color: '#909399',
marginBottom: 8,
},
timeInputButton: {
width: 40,
height: 40,
backgroundColor: '#F5F7FA',
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 8,
},
timeInputButtonText: {
fontSize: 20,
fontWeight: '600',
color: '#409EFF',
},
timeInputValue: {
fontSize: 24,
fontWeight: '700',
color: '#303133',
marginBottom: 8,
},
applyButton: {
backgroundColor: '#409EFF',
paddingVertical: 12,
borderRadius: 8,
alignItems: 'center',
},
applyButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#FFFFFF',
},
// ======== 信息卡片 ========
infoCard: {
backgroundColor: '#FFFFFF',
borderRadius: 12,
padding: 16,
margin: 16,
marginTop: 0,
shadowColor: '#000000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.08,
shadowRadius: 8,
elevation: 4,
},
infoTitle: {
fontSize: 16,
fontWeight: '600',
color: '#303133',
marginBottom: 12,
},
infoText: {
fontSize: 14,
color: '#606266',
lineHeight: 22,
marginBottom: 6,
},
});
export default Timer;
三、核心实现要点
3.1 时间格式化
将秒数格式化为 HH:MM:SS 格式:
javascript
const formatTime = useCallback((totalSeconds: number): string => {
const h = Math.floor(totalSeconds / 3600);
const m = Math.floor((totalSeconds % 3600) / 60);
const s = totalSeconds % 60;
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
}, []);
格式化要点:
- 使用
Math.floor计算小时、分钟、秒 - 使用
padStart(2, '0')确保两位数显示 - 返回 HH:MM:SS 格式的字符串
3.2 倒计时逻辑
使用 useEffect 和 setInterval 实现倒计时:
javascript
useEffect(() => {
if (isRunning && remainingSeconds > 0) {
timerRef.current = setInterval(() => {
setRemainingSeconds(prev => {
if (prev <= 1) {
setIsRunning(false);
return 0;
}
return prev - 1;
});
}, 1000);
} else {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
}
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
}, [isRunning, remainingSeconds]);
倒计时要点:
- 使用 useRef 存储定时器引用
- 每秒减少剩余时间
- 时间到达 0 时自动停止
- 组件卸载时清除定时器
3.3 时间设置
允许用户设置小时、分钟、秒:
javascript
const [hours, setHours] = useState(0);
const [minutes, setMinutes] = useState(5);
const [seconds, setSeconds] = useState(0);
const applySettings = useCallback(() => {
const newTotal = hours * 3600 + minutes * 60 + seconds;
if (newTotal > 0) {
setIsRunning(false);
setTotalSeconds(newTotal);
setRemainingSeconds(newTotal);
}
}, [hours, minutes, seconds]);
设置要点:
- 分别管理小时、分钟、秒
- 使用 + 和 - 按钮调整时间
- 应用设置时计算总秒数
- 限制时间范围(小时 0-23,分钟和秒 0-59)
3.4 进度条显示
显示剩余时间的进度:
javascript
const progress = totalSeconds > 0 ? ((totalSeconds - remainingSeconds) / totalSeconds) * 100 : 0;
<View style={styles.progressBar}>
<View style={[styles.progressFill, { width: `${progress}%` }]} />
</View>
进度条要点:
- 计算已过时间的百分比
- 使用 width 属性控制进度条长度
- 使用百分比字符串设置宽度
四、性能优化
4.1 使用 useCallback 优化
使用 useCallback 缓存回调函数:
javascript
const formatTime = useCallback((totalSeconds: number): string => {
const h = Math.floor(totalSeconds / 3600);
const m = Math.floor((totalSeconds % 3600) / 60);
const s = totalSeconds % 60;
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
}, []);
const startTimer = useCallback(() => {
if (isRunning || remainingSeconds <= 0) return;
setIsRunning(true);
}, [isRunning, remainingSeconds]);
为什么使用 useCallback?
- 避免每次渲染都创建新函数
- 减少子组件的重新渲染
- 提升整体性能
4.2 使用 useRef 存储定时器
使用 useRef 存储定时器引用:
javascript
const timerRef = useRef<NodeJS.Timeout | null>(null);
timerRef.current = setInterval(() => {
setRemainingSeconds(prev => prev - 1);
}, 1000);
为什么使用 useRef?
- 避免定时器引用在重新渲染时丢失
- 确保能够正确清除定时器
- 避免内存泄漏
4.3 使用 memo 优化
使用 memo 包装组件:
javascript
const Timer = memo(() => {
// ...
});
为什么使用 memo?
- 避免不必要的重新渲染
- 提升整体性能
- 在复杂应用中效果更明显
五、常见问题与解决方案
5.1 定时器不停止
问题现象: 组件卸载后定时器仍在运行
可能原因:
- 没有清除定时器
- 清除定时器的逻辑不正确
解决方案:
javascript
useEffect(() => {
if (isRunning && remainingSeconds > 0) {
timerRef.current = setInterval(() => {
setRemainingSeconds(prev => prev - 1);
}, 1000);
}
return () => {
if (timerRef.current) {
clearInterval(timerRef.current);
timerRef.current = null;
}
};
}, [isRunning, remainingSeconds]);
5.2 时间显示不正确
问题现象: 时间显示格式不正确
可能原因:
- 格式化逻辑错误
- 没有使用 padStart
解决方案:
javascript
const formatTime = useCallback((totalSeconds: number): string => {
const h = Math.floor(totalSeconds / 3600);
const m = Math.floor((totalSeconds % 3600) / 60);
const s = totalSeconds % 60;
return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;
}, []);
5.3 进度条不更新
问题现象: 进度条不随时间变化
可能原因:
- 进度计算逻辑错误
- 没有正确更新状态
解决方案:
javascript
const progress = totalSeconds > 0 ? ((totalSeconds - remainingSeconds) / totalSeconds) * 100 : 0;
<View style={styles.progressBar}>
<View style={[styles.progressFill, { width: `${progress}%` }]} />
</View>
六、扩展用法
6.1 添加音效提醒
添加时间到达时的音效提醒:
javascript
import { Sound } from 'react-native-sound';
const playAlarm = useCallback(() => {
const sound = new Sound('alarm.mp3', Sound.MAIN_BUNDLE, (error) => {
if (error) {
console.log('Failed to load sound', error);
return;
}
sound.play();
});
}, []);
useEffect(() => {
if (remainingSeconds === 0) {
playAlarm();
}
}, [remainingSeconds, playAlarm]);
6.2 添加震动提醒
添加时间到达时的震动提醒:
javascript
import { Vibration } from 'react-native';
useEffect(() => {
if (remainingSeconds === 0) {
Vibration.vibrate([500, 200, 500]); // 震动模式
}
}, [remainingSeconds]);
6.3 添加预设时间
添加常用的时间预设:
javascript
const presets = [
{ label: '1分钟', seconds: 60 },
{ label: '5分钟', seconds: 300 },
{ label: '10分钟', seconds: 600 },
{ label: '15分钟', seconds: 900 },
{ label: '30分钟', seconds: 1800 },
];
<View style={styles.presetsContainer}>
{presets.map(preset => (
<TouchableOpacity
key={preset.label}
style={styles.presetButton}
onPress={() => {
setTotalSeconds(preset.seconds);
setRemainingSeconds(preset.seconds);
setIsRunning(false);
}}
>
<Text style={styles.presetButtonText}>{preset.label}</Text>
</TouchableOpacity>
))}
</View>
七、总结
计时器是一个实用的时间管理工具,通过本篇文章,我们学习了:
- 时间格式化:将秒数格式化为 HH:MM:SS 格式
- 倒计时逻辑:使用 useEffect 和 setInterval 实现倒计时
- 时间设置:允许用户设置小时、分钟、秒
- 进度显示:实时显示剩余时间的进度
- 性能优化:使用 useCallback、useRef、memo 优化性能
- 错误处理:正确处理定时器的清除
计时器组件在 React Native for Harmony 中表现良好,运行稳定,是一个很好的学习案例。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net