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

使用自定义Hook加载Firebase Storage图片时报无效Hook调用错误

问题原因

报错的核心原因是违反了React Hook的调用规则:Hook仅允许在React函数组件的顶层、或自定义Hook的顶层调用。你当前在FlatList的renderItem回调函数内部调用useImageSource,renderItem是组件内部的嵌套函数,不属于组件顶层调用场景,因此触发无效Hook调用的报错。

解决方案

推荐将列表项逻辑抽离为独立的子组件,在子组件顶层调用自定义Hook,符合Hook调用规则:

  1. 抽离独立的WorkoutItem子组件
import { TouchableOpacity, View, Image, Text } from "react-native";
import { Fontisto } from "@expo/vector-icons";
import ReactNativeHapticFeedback from "react-native-haptic-feedback";
import useImageSource from "@flexeee:common/utils/hooks/useImageSource";
import { Colors } from "@flexeee:common/theme/Colors";
import { Images } from "@flexeee:assets/images/Images";

const hapticOptions = {
  enableVibrateFallback: true,
  ignoreAndroidSystemSettings: false,
};

function WorkoutItem({ item, navigation }) {
  // Hook在组件顶层调用,符合规则
  const imageSrc = useImageSource(item.photo, Images.upperBody);

  return (
    <TouchableOpacity
      style={{ marginBottom: 20 }}
      onPress={() => {
        navigation.navigate("DetailStack", {
          screen: "WorkoutDetails",
          params: { item: item },
        });
        ReactNativeHapticFeedback.trigger("impactLight", hapticOptions);
      }}
    >
      <View style={styles.workoutImageContainer}>
        {item.isLocked ? (
          <View style={{ backgroundColor: Colors.black, borderRadius: 10 }}>
            <Image
              source={imageSrc}
              resizeMode="cover"
              style={styles.workoutImage}
              opacity={0.75}
            />
          </View>
        ) : (
          <Image
            source={imageSrc}
            resizeMode="cover"
            style={styles.workoutImage}
          />
        )}
        <View style={styles.durationContainer}>
          <Text style={styles.title}>{item.duration}</Text>
        </View>
      </View>
      {item.isLocked && (
        <View style={styles.lockContainer}>
          <Fontisto name="locked" size={28} color={Colors.white} />
        </View>
      )}
      <View style={styles.subtitleContainer}>
        <Text style={styles.subtitle}>{item.name}</Text>
      </View>
    </TouchableOpacity>
  );
}
  1. 修改原SubCategories组件的renderItem逻辑
export default function SubCategories({ navigation, workouts, category }) {
  const renderItem = ({ item }) => {
    return <WorkoutItem item={item} navigation={navigation} />
  };

  // 剩余原有逻辑保持不变
  return (
    <View>
      <View style={styles.headingContainer}>
        <Text style={styles.heading}>{category.name}</Text>
        <TouchableOpacity
          onPress={() => {
            navigation.navigate("DetailStack", {
              screen: "WorkoutList",
              params: { category: category, workouts: workouts },
            });
            ReactNativeHapticFeedback.trigger("impactLight", options);
          }}
        >
          <Text style={styles.viewOption}>查看全部</Text>
        </TouchableOpacity>
      </View>
      <FlatList
        data={workouts}
        horizontal
        keyExtractor={(item) => `${item.id}`}
        renderItem={renderItem}
        contentContainerStyle={{}}
        showsHorizontalScrollIndicator={false}
      />
    </View>
  );
}
额外优化建议

你当前useImageSource中每次Hook调用都会初始化一次storage实例,可将其提到模块顶层,减少不必要的依赖变动:

import React, { useState, useEffect } from "react";
import firebase from "@react-native-firebase/app";
import "@react-native-firebase/storage";
import FastImage from "react-native-fast-image";
import { Images } from "@flexeee:assets/images/Images";

// 放到模块顶层,仅初始化一次
const storage = firebase.storage();

export default function useImageSource(image, defaultImage) {
  const [imageSource, setImageSource] = useState();

  useEffect(() => {
    async function getImageSource() {
      try {
        const url = await storage.ref(`images/${image}.png`).getDownloadURL();
        setImageSource({
          uri: url,
          priority: FastImage.priority.normal,
        });
      } catch (err) {
        setImageSource(defaultImage ?? Images.upperBody);
      }
    }

    getImageSource();
  // 移除storage依赖
  }, [image, defaultImage]);

  return imageSource;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 01:45:03