You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 14:52:39