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

React Native卡片独立定时器状态更新、停止与存储问题求助

修复React Native卡片独立定时器的状态与逻辑问题

原代码核心问题

  • 所有卡片共享timer、isPlaying等全局状态,无法实现独立的计时控制
  • 状态更新异步,handleCardClick中调用setCurrentCard后立刻读取currentCard会拿到旧值,导致逻辑判断错误
  • 直接修改tutorApi数组元素的time属性,不会触发组件重新渲染,且存储时机错误
  • 定时器管理混乱,切换卡片时可能存在未清理的定时器,导致计时异常
  • 同一张卡片点击重置的逻辑因状态异步问题无法正确执行

修复后的完整代码

import { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, FlatList, Image } from 'react-native';

const CollegeTeacherGandhinagar = ({ navigation }) => {
  // 用对象存储每个卡片的独立状态:timer(计时)、isPlaying(是否播放)、intervalId(定时器ID)
  const [cardTimers, setCardTimers] = useState({});
  // 当前激活的卡片ID
  const [activeCardId, setActiveCardId] = useState(null);
  // 存储所有课程数据(保持不可变性,避免直接修改原数组)
  const [courses, setCourses] = useState(tutorApi);

  // 格式化时间为MM:SS
  const formatTime = (time) => {
    const minutes = Math.floor(time / 60).toString().padStart(2, '0');
    const seconds = (time % 60).toString().padStart(2, '0');
    return `${minutes}:${seconds}`;
  };

  // 启动指定卡片的定时器
  const startTimer = (cardId) => {
    setCardTimers(prev => {
      // 如果该卡片已有定时器,先清理
      if (prev[cardId]?.intervalId) clearInterval(prev[cardId].intervalId);
      const intervalId = setInterval(() => {
        setCardTimers(innerPrev => ({
          ...innerPrev,
          [cardId]: {
            ...innerPrev[cardId],
            timer: innerPrev[cardId].timer + 1
          }
        }));
      }, 1000);
      return {
        ...prev,
        [cardId]: {
          ...prev[cardId],
          isPlaying: true,
          intervalId
        }
      };
    });
  };

  // 停止指定卡片的定时器
  const stopTimer = (cardId) => {
    setCardTimers(prev => {
      if (prev[cardId]?.intervalId) clearInterval(prev[cardId].intervalId);
      return {
        ...prev,
        [cardId]: {
          ...prev[cardId],
          isPlaying: false,
          intervalId: null
        }
      };
    });
  };

  // 重置指定卡片的计时
  const resetTimer = (cardId) => {
    setCardTimers(prev => ({
      ...prev,
      [cardId]: {
        ...prev[cardId],
        timer: 0
      }
    }));
  };

  // 存储指定卡片的计时到课程数据中
  const storeTimer = (cardId, time) => {
    setCourses(prevCourses => 
      prevCourses.map(course => 
        course.id === cardId ? { ...course, time } : course
      )
    );
  };

  // 卡片点击事件处理
  const handleCardClick = (item) => {
    const cardId = item.id;

    // 如果当前有激活的卡片,先停止它的定时器并存储计时
    if (activeCardId && activeCardId !== cardId) {
      stopTimer(activeCardId);
      const activeTimer = cardTimers[activeCardId]?.timer || 0;
      storeTimer(activeCardId, activeTimer);
    }

    // 处理当前点击的卡片
    if (activeCardId === cardId) {
      // 点击的是当前激活的卡片:重置计时并重启
      resetTimer(cardId);
      startTimer(cardId);
    } else {
      // 点击的是新卡片:初始化计时(如果没有则设为0)并启动
      if (!cardTimers[cardId]) {
        setCardTimers(prev => ({
          ...prev,
          [cardId]: { timer: 0, isPlaying: false, intervalId: null }
        }));
      }
      startTimer(cardId);
      setActiveCardId(cardId);
    }
  };

  // 组件卸载时清理所有定时器
  useEffect(() => {
    return () => {
      Object.values(cardTimers).forEach(timer => {
        if (timer.intervalId) clearInterval(timer.intervalId);
      });
    };
  }, [cardTimers]);

  // 渲染单个卡片
  const courseCard = ({ item }) => {
    const cardId = item.id;
    const cardState = cardTimers[cardId] || { timer: 0, isPlaying: false };
    const isActive = activeCardId === cardId;

    return (
      <View style={{ margin: 10 }}>
        <TouchableOpacity
          style={{
            backgroundColor: isActive ? '#ff5435' : '#D9D9D9',
            padding: 10,
            borderRadius: 10,
          }}
          onPress={() => handleCardClick(item)}>
          <Text style={{ fontWeight: 'bold', fontSize: 18 }}>{item.title}</Text>
          <Text>{item.description}</Text>
          {isActive && (
            <View style={{ flexDirection: 'row', marginTop: 10, alignItems: 'center' }}>
              <Text style={{ marginRight: 10 }}>{formatTime(cardState.timer)}</Text>
              <TouchableOpacity 
                onPress={() => cardState.isPlaying ? stopTimer(cardId) : startTimer(cardId)}
              >
                <Image
                  source={
                    cardState.isPlaying
                      ? require('../../asset/paus.png')
                      : require('../../asset/play.png')
                  }
                  style={{ width: 20, height: 20 }}
                />
              </TouchableOpacity>
            </View>
          )}
        </TouchableOpacity>
      </View>
    );
  };

  return (
    <FlatList
      keyExtractor={item => item.id}
      data={courses}
      renderItem={courseCard}
    />
  );
};

关键修改说明

  1. 独立卡片状态管理:用cardTimers对象存储每个卡片的计时、播放状态和定时器ID,确保每个卡片的状态完全独立,互不干扰
  2. 异步状态修正:所有状态更新都使用函数式更新(setState(prev => ...)),确保获取到最新的状态值,避免异步更新导致的逻辑错误
  3. 数据不可变性:更新课程计时数据时,通过map创建新数组,确保组件能感知到数据变化并重新渲染
  4. 完善定时器清理:切换卡片时自动停止旧卡片的定时器,组件卸载时清理所有残留定时器,避免内存泄漏和计时混乱
  5. 明确交互逻辑:
    • 点击新卡片:自动停止并存储旧卡片计时,启动新卡片的定时器
    • 点击当前激活卡片:重置计时并重启定时器
    • 支持手动暂停/继续当前卡片的计时

内容的提问来源于stack exchange,提问作者Manav Dave

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 09:58:09