React Native计时统计页报错:Cannot read property 'week' of undefined
React Native计时应用统计页面报错:Can’t read property 'week' of undefined
问题详情
开发React Native计时应用时,统计页面需展示计时器本周、本月及今日的启动时长,但运行时触发错误:Can’t read property 'week' of undefined,错误定位在Statistic模块。
安卓模拟器报错截图:
相关组件代码
Timer组件代码
import React, { useState, useEffect } from "react"; import { Image, View, Button, Text } from "react-native"; import TextStyle from "../constants/style/TextStyle"; import ProgressCircle from "react-native-progress-circle"; import { Vibration } from 'react-native'; const formatTime = (seconds) => { const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; return `${minutes}:${remainingSeconds < 10 ? "0" : ""}${remainingSeconds}`; }; const Timer = () => { const focusDuration = 30 * 60; // 30分钟(秒数) const breakDuration = 5 * 60; // 5分钟(秒数) const [isRunning, setIsRunning] = useState(false); const [isPaused, setIsPaused] = useState(false); const [isFocusTime, setIsFocusTime] = useState(true); const [currentPhase, setCurrentPhase] = useState('focus'); // 'focus' 或 'break' const [elapsedSeconds, setElapsedSeconds] = useState(0); const [remainingTime, setRemainingTime] = useState(focusDuration); // 初始值应为focusDuration // 存储计时器使用数据的状态 const [usageData, setUsageData] = useState({ week: 0, // 本周用时(秒) today: 0, // 今日用时(秒) month: 0, // 本月用时(秒) }); useEffect(() => { let interval; if (isRunning && !isPaused) { interval = setInterval(() => { setElapsedSeconds((prevSeconds) => { if (prevSeconds >= remainingTime) { clearInterval(interval); setIsRunning(false); setIsPaused(false); if (currentPhase === 'focus') { setCurrentPhase('break'); setElapsedSeconds(0); setRemainingTime(breakDuration); setIsFocusTime(false); // 休息阶段设置为false } else { setCurrentPhase('focus'); setElapsedSeconds(0); setRemainingTime(focusDuration); setIsFocusTime(true); // 专注阶段设置为true } // 自动启动下一阶段计时器 setIsRunning(true); // 休息阶段震动3次 if (currentPhase === 'break') { for (let i = 0; i < 3; i++) { setTimeout(() => { Vibration.vibrate(500); }, i * 1000); } } // 根据当前阶段更新使用数据 setUsageData((prevData) => ({ ...prevData, week: currentPhase === 'focus' ? prevData.week + focusDuration : prevData.week + breakDuration, today: currentPhase === 'focus' ? prevData.today + focusDuration : prevData.today + breakDuration, month: currentPhase === 'focus' ? prevData.month + focusDuration : prevData.month + breakDuration, })); } else { return prevSeconds + 1; } }); }, 1000); } else { clearInterval(interval); } return () => { clearInterval(interval); }; }, [isRunning, isPaused, currentPhase, remainingTime]); const handleStart = () => { setIsRunning(true); setIsPaused(false); }; const handlePause = () => { setIsPaused(true); }; const handleResume = () => { setIsPaused(false); }; const handleCancel = () => { setIsRunning(false); setIsPaused(false); setElapsedSeconds(0); }; const maxDuration = isFocusTime ? focusDuration : breakDuration; const animationPercentage = (elapsedSeconds / maxDuration) * 100; return ( <View style={TextStyle.container}> <View style={TextStyle.threeLines} > <Image source={require('../assets/ThreeLines.png')} ></Image> </View> <View style={TextStyle.blackCircleImage} > <View style={TextStyle.outerCircle}> <ProgressCircle percent={100 - animationPercentage} radius={150} borderWidth={2} color="#fff" shadowColor='#000' bgColor="#000" rotation={90} clockwise={false} > <Text style={TextStyle.timeText}> {formatTime(maxDuration - elapsedSeconds)} </Text> <Text style={TextStyle.focusText}> {isFocusTime ? 'FOCUS' : 'BREAK'} </Text> </ProgressCircle> </View> </View> <View style={TextStyle.buttonStartView}> {!isRunning && ( <Button color={'#fff'} title="START" onPress={handleStart} /> )} {isRunning && !isPaused && ( <> <Button color={'#fff'} title="PAUSE" onPress={handlePause} /> <Button color={'#fff'} title="STOP" onPress={handleCancel} /> </> )} {isRunning && isPaused && ( <> <Button color={'#fff'} title="RESUME" onPress={handleResume} /> <Button color={'#fff'} title="STOP" onPress={handleCancel} /> </> )} </View> <View style={TextStyle.bottomText}> <Text style={TextStyle.bottomTimeText}> <Text style={TextStyle.smallText}>Focus{'\n'}</Text> {formatTime(focusDuration)}{'\n'} <Text style={TextStyle.smallText}>Break{'\n'}</Text> {formatTime(breakDuration)} </Text> </View> {/* <Stats usageData={usageData} /> */} </View> ) } export default Timer
Statistic组件代码
import React from 'react'; import { View, Text } from 'react-native'; const formatTimeInHoursMinutes = (seconds) => { const hours = Math.floor(seconds / 3600); const minutes = Math.floor((seconds % 3600) / 60); return `${hours}h ${minutes}m`; }; const Statistic = ({ usageData }) => { return ( <View> <Text>Stats</Text> <Text style={{ color: 'black' }}>This Week: {formatTimeInHoursMinutes(usageData.week)} </Text> <Text style={{ color: 'black' }}>Today: {formatTimeInHoursMinutes(usageData.today)} </Text> <Text style={{ color: 'black' }}>This Month: {formatTimeInHoursMinutes(usageData.month)} </Text> </View> ); } export default Statistic
Stats组件代码
import React from 'react'; import { View } from 'react-native'; import Statistic from './Statistic'; const Stats = ({ usageData }) => { return ( <View> <Statistic usageData={usageData} /> </View> ); } export default Stats;
问题原因及解决方法
核心原因
Statistic组件中直接访问usageData.week,但usageData未被正确传入组件,导致其值为undefined,触发属性读取错误。
具体修复步骤
启用Timer组件中的Stats引用
取消Timer组件中<Stats usageData={usageData} />的注释,确保统计组件能接收到Timer中维护的usageData状态:// Timer组件return部分取消注释 <Stats usageData={usageData} />添加参数默认值防护
在Statistic组件中为usageData设置默认值,避免未传入或传入undefined时报错:const Statistic = ({ usageData = { week: 0, today: 0, month: 0 } }) => { // 原有组件逻辑保持不变 }或者使用可选链操作符做安全访问:
<Text style={{ color: 'black' }}>This Week: {formatTimeInHoursMinutes(usageData?.week || 0)} </Text> <Text style={{ color: 'black' }}>Today: {formatTimeInHoursMinutes(usageData?.today || 0)} </Text> <Text style={{ color: 'black' }}>This Month: {formatTimeInHoursMinutes(usageData?.month || 0)} </Text>路由跳转场景的数据传递检查
如果Stats页面是通过路由跳转打开的(比如使用React Navigation),需确保跳转时正确传递usageData:- 跳转时传递数据:
navigation.navigate('Stats', { usageData: usageData }) - 在Stats页面接收数据:
const Stats = ({ route }) => { const { usageData = { week: 0, today: 0, month: 0 } } = route.params || {}; return ( <View> <Statistic usageData={usageData} /> </View> ); }
- 跳转时传递数据:
额外优化建议
- 修正统计逻辑准确性:当前
usageData仅在阶段结束时一次性增加固定时长,若用户中途暂停或取消计时器,统计数据会失真。建议改为每秒累计实际运行时长:// 在Timer组件的useEffect定时器中,更新elapsedSeconds时同步更新usageData setElapsedSeconds((prevSeconds) => { // 同步更新统计数据 setUsageData(prev => ({ ...prev, week: prev.week + 1, today: prev.today + 1, month: prev.month + 1 })); return prevSeconds + 1; }); - 持久化统计数据:使用
AsyncStorage或其他存储方案保存usageData,避免应用重启后数据丢失。
内容的提问来源于stack exchange,提问作者Ismail Masood Orakzai
相关产品推荐
相关产品推荐

