React Native答题应用计时器卡顿问题求助及解决
答题应用计时器卡顿问题解决方案
核心问题诊断
计时器启动后页面卡顿,停止后恢复正常,几乎可以确定是计时器的实现方式导致组件过于频繁重渲染——比如每毫秒就调用setState更新时间,让React Native的JS线程一直处于繁忙状态,无法及时响应用户的点击操作。
具体修复方案
1. 用useRef替代useState存储计时器核心数据
useState每次更新都会触发组件重渲染,而useRef的更新不会。我们可以用useRef保存当前时间值和计时器ID,只在需要更新显示的时候才调用setState。
示例代码:
import { useState, useRef, useEffect } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; const QuizScreen = () => { const [currentQuestion, setCurrentQuestion] = useState(0); const [displayTime, setDisplayTime] = useState(0); // 仅用于显示的状态 const timeRef = useRef(0); // 存储实际时间,更新不触发重渲染 const intervalRef = useRef(null); // 启动计时器 const startTimer = () => { timeRef.current = 0; intervalRef.current = setInterval(() => { timeRef.current += 100; // 每100ms更新一次ref // 每1秒更新一次显示状态,减少重渲染次数 if (timeRef.current % 1000 === 0) { setDisplayTime(prev => prev + 1); } }, 100); }; // 停止计时器 const stopTimer = () => { if (intervalRef.current) { clearInterval(intervalRef.current); intervalRef.current = null; } }; // 切换下一题 const handleNextQuestion = () => { if (currentQuestion === 4) { stopTimer(); // 完成答题逻辑 } else { setCurrentQuestion(prev => prev + 1); } }; useEffect(() => { startTimer(); return () => stopTimer(); // 组件卸载时清理计时器 }, []); return ( <View> <Text>已用时:{displayTime}秒</Text> {/* 题目和选项渲染 */} <TouchableOpacity onPress={handleNextQuestion}> <Text>下一题</Text> </TouchableOpacity> </View> ); }; export default QuizScreen;
2. 用requestAnimationFrame替代setInterval(更推荐)
setInterval在React Native的JS线程中可能存在精度问题,且容易因为线程阻塞导致任务堆积。requestAnimationFrame会和UI渲染同步,性能更稳定。
示例代码:
import { useState, useRef, useEffect } from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; const QuizScreen = () => { const [currentQuestion, setCurrentQuestion] = useState(0); const [displayTime, setDisplayTime] = useState(0); const timeRef = useRef({ startTime: Date.now(), elapsed: 0 }); const animationFrameRef = useRef(null); const updateTimer = () => { const now = Date.now(); timeRef.current.elapsed += now - timeRef.current.startTime; timeRef.current.startTime = now; // 控制显示更新频率,比如每500ms更新一次 if (timeRef.current.elapsed % 500 === 0) { setDisplayTime(Math.floor(timeRef.current.elapsed / 1000)); } animationFrameRef.current = requestAnimationFrame(updateTimer); }; const startTimer = () => { timeRef.current.startTime = Date.now(); timeRef.current.elapsed = 0; animationFrameRef.current = requestAnimationFrame(updateTimer); }; const stopTimer = () => { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); animationFrameRef.current = null; } }; const handleNextQuestion = () => { if (currentQuestion === 4) { stopTimer(); } else { setCurrentQuestion(prev => prev + 1); } }; useEffect(() => { startTimer(); return () => stopTimer(); }, []); return ( <View> <Text>已用时:{displayTime}秒</Text> {/* 题目和选项 */} <TouchableOpacity onPress={handleNextQuestion}> <Text>下一题</Text> </TouchableOpacity> </View> ); }; export default QuizScreen;
3. 额外优化点
- 检查题目/选项的渲染逻辑:如果选项是循环渲染的,用
memo包裹选项组件,避免不必要的重渲染:const OptionItem = React.memo(({ option, onPress }) => ( <TouchableOpacity onPress={() => onPress(option)}> <Text>{option}</Text> </TouchableOpacity> )); - 避免在计时器回调中执行复杂计算:所有耗时操作都要移出计时器回调,防止阻塞JS线程。
内容的提问来源于stack exchange,提问作者lychar20
相关产品推荐
相关产品推荐

