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

如何访问Firestore引用文档数据?渲染异常求助

问题分析与解决方案

核心问题点

  1. 无限渲染/闪烁:直接在渲染阶段的map循环中调用getData异步函数,每次组件渲染都会触发状态更新,进而引发重新渲染,形成循环。
  2. 状态覆盖:单一的exerciseData状态会被多个exercise请求覆盖,导致所有exercise条目显示相同的最新数据,无法对应到各自的关联文档。
  3. 异步状态不同步:getWorkouts中调用workouts.map时,setWorkouts是异步操作,此时workouts仍为旧状态,无法正确遍历新获取的训练数据。
  4. 变量初始化问题:组件内的workoutsArray是普通变量,每次渲染都会重新初始化,可能导致数据异常。

解决方案一:预加载所有关联Exercise数据(推荐)

在获取Workouts数据时,直接批量加载关联的Exercise文档并整合到Workouts对象中,避免渲染阶段的异步请求。

修改后的完整代码

export function WorkoutDisplayScreen({ navigation }) {
  const [workouts, setWorkouts] = React.useState([]);

  const getWorkouts = async () => {
    console.log('Firebase READ & WRITE');
    const workoutsCollection = collection(db, 'Users', auth.currentUser.uid, 'Workouts');
    const workoutsSnapshot = await getDocs(workoutsCollection);
    
    // 批量整合Workout与关联的Exercise数据
    const workoutsData = await Promise.all(
      workoutsSnapshot.docs.map(async (workoutDoc) => {
        const workout = workoutDoc.data();
        // 获取当前Workout下所有Exercise的文档数据
        const exercisesData = await Promise.all(
          workout.exercises.map(async (exerciseRef) => {
            const exerciseDoc = await getDoc(exerciseRef);
            return exerciseDoc.data();
          })
        );
        return { ...workout, exercises: exercisesData };
      })
    );

    setWorkouts(workoutsData);
  };

  useEffect(() => {
    console.log('Rendering...');
    getWorkouts();
  }, []);

  return (
    <ScreenContainer>
      <View style={styles.headerrow}>
        <TouchableOpacity onPress={() => navigation.goBack()}>
          <AntDesign name="leftcircle" size={24} color="black" style={styles.headericon} />
        </TouchableOpacity>
        <Text style={styles.header}>My Workouts</Text>
      </View>

      {workouts.length > 0 &&
        workouts.map((workout) => (
          <View key={workout.id}>
            <View style={styles.workoutnamecontainer}>
              <Text style={styles.workoutnametext}>{workout.name}</Text>
            </View>
            {workout.exercises.map((exercise, index) => (
              <View style={styles.exercisecontainer} key={index}>
                <Text style={styles.exercisetext}>{exercise.name}</Text>
                <Text style={styles.exercisetext}>{exercise.type}</Text>
                <Text style={styles.exercisetext}>{exercise.weight}</Text>
                <Text style={styles.exercisetext}>{exercise.reps}</Text>
              </View>
            ))}
          </View>
        ))}
    </ScreenContainer>
  );
}

解决方案二:按需加载Exercise数据(适合大数据量场景)

使用对象存储每个Exercise的文档数据,通过useEffect在Workouts数据更新时触发加载,避免一次性请求所有数据。

修改后的核心代码片段

export function WorkoutDisplayScreen({ navigation }) {
  const [workouts, setWorkouts] = React.useState([]);
  const [exerciseDataMap, setExerciseDataMap] = useState({}); // 存储Exercise ID与对应数据的映射

  const getWorkouts = async () => {
    console.log('Firebase READ & WRITE');
    const workoutsCollection = collection(db, 'Users', auth.currentUser.uid, 'Workouts');
    const workoutsSnapshot = await getDocs(workoutsCollection);
    const workoutsData = workoutsSnapshot.docs.map(workoutDoc => workoutDoc.data());
    setWorkouts(workoutsData);
  };

  // 当Workouts更新时,加载未获取的Exercise数据
  useEffect(() => {
    const loadExercises = async () => {
      const newExerciseMap = {...exerciseDataMap};
      await Promise.all(
        workouts.flatMap(workout => 
          workout.exercises.map(async (exerciseRef) => {
            const exerciseId = exerciseRef.id;
            if (!newExerciseMap[exerciseId]) {
              const exerciseDoc = await getDoc(exerciseRef);
              newExerciseMap[exerciseId] = exerciseDoc.data();
            }
          })
        )
      );
      setExerciseDataMap(newExerciseMap);
    };

    if (workouts.length > 0) loadExercises();
  }, [workouts]);

  // 渲染部分
  return (
    <ScreenContainer>
      {/* 头部代码省略 */}
      {workouts.length > 0 &&
        workouts.map((workout) => (
          <View key={workout.id}>
            <View style={styles.workoutnamecontainer}>
              <Text style={styles.workoutnametext}>{workout.name}</Text>
            </View>
            {workout.exercises.map((exerciseRef, index) => {
              const exercise = exerciseDataMap[exerciseRef.id];
              if (!exercise) return <Text key={index}>加载中...</Text>;
              return (
                <View style={styles.exercisecontainer} key={index}>
                  <Text style={styles.exercisetext}>{exercise.name}</Text>
                  <Text style={styles.exercisetext}>{exercise.type}</Text>
                  <Text style={styles.exercisetext}>{exercise.weight}</Text>
                  <Text style={styles.exercisetext}>{exercise.reps}</Text>
                </View>
              );
            })}
          </View>
        ))}
    </ScreenContainer>
  );
}

额外优化说明

  • 移除了冗余的isFirstLoad状态,空依赖的useEffect会在组件挂载时自动执行一次getWorkouts。
  • 所有临时数组变量(如workoutsArray)都移到函数内部声明,避免每次渲染重新初始化。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 18:49:10