使用自定义Hook加载Firebase Storage图片时报无效Hook调用错误
问题原因
报错的核心原因是违反了React Hook的调用规则:Hook仅允许在React函数组件的顶层、或自定义Hook的顶层调用。你当前在FlatList的renderItem回调函数内部调用useImageSource,renderItem是组件内部的嵌套函数,不属于组件顶层调用场景,因此触发无效Hook调用的报错。
解决方案
推荐将列表项逻辑抽离为独立的子组件,在子组件顶层调用自定义Hook,符合Hook调用规则:
- 抽离独立的
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> ); }
- 修改原
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
相关产品推荐
相关产品推荐

