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

React Native自定义CameraRoll Hook调用报错求助及代码优化

问题分析与修正方案

你遇到的问题核心是违反了React Hook的基本规则,我来一步步拆解你的错误,再给你正确的实现方式:

错误点拆解

1. 第一次错误:在异步函数中调用Hook

React Hook明确规定:只能在函数组件的顶层作用域调用Hook,绝对不能在异步函数、循环、条件判断或者嵌套函数里调用。你最初在_pickImage这个异步函数里调用useCameraRoll,直接触发了Invalid hook call错误,这是Hook规则的红线。

2. 第二次错误:组件嵌套定义+错误的 onPress 绑定

  • 你在RenderImagePicker内部定义了MyComponent,然后把MyComponent传给onPress——onPress需要的是一个事件处理函数,而不是一个组件。这样写不仅不会触发相册加载,还会导致组件逻辑混乱。
  • 你把_handleImagePicked、_maybeRenderUploadingOverlay放在了组件外部,这些函数需要访问组件内部的状态(比如uploading),放在外部会导致作用域错误,无法正确获取状态。

正确的实现代码

我们把所有逻辑整理到同一个函数组件内部,严格遵循Hook规则:

import { useState, useCallback } from 'react';
import { TouchableOpacity, Text, View, ActivityIndicator, Image } from 'react-native';
import { useCameraRoll } from '../shared/hooks';

const RenderImagePicker = ({ 
  setFieldValue,
  name,
  value,
  label,
  meta: { touched, error },
}) => {
  const [uploading, setUploading] = useState(false);
  // 1. 在组件顶层调用Hook,符合规则
  const [photos, getPhotos] = useCameraRoll({ first: 80 });

  // 2. 图片上传逻辑放在组件内部,确保能访问到组件状态
  const uploadImageAsync = useCallback(async (uri) => {
    const apiUrl = 'http://xx:3000/upload';
    const uriParts = uri.split('.');
    const fileType = uriParts[uriParts.length - 1];
    const formData = new FormData();
    formData.append('photo', { 
      uri, 
      name: `photo.${fileType}`, 
      type: `image/${fileType}` 
    });
    const options = {
      method: 'POST',
      body: formData,
      headers: {
        Accept: 'application/json',
        'Content-Type': 'multipart/form-data',
      },
    };
    return fetch(apiUrl, options);
  }, []);

  const _handleImagePicked = useCallback(async (pickerResult) => {
    try {
      setUploading(true);
      if (!pickerResult.cancelled) {
        const uploadResponse = await uploadImageAsync(pickerResult.uri);
        const uploadResult = await uploadResponse.json();
        setFieldValue(name, uploadResult.location);
      }
    } catch (e) {
      console.log(e);
      alert('Upload failed, sorry :(');
    } finally {
      setUploading(false);
    }
  }, [setFieldValue, name, uploadImageAsync]);

  // 3. 加载遮罩函数放在组件内部,能访问uploading状态
  const _maybeRenderUploadingOverlay = useCallback(() => {
    if (uploading) {
      return (
        <View>
          <ActivityIndicator 
            color="#ff0000" 
            size="large" 
            style={{ alignSelf: 'center', flex: 1 }} 
          />
        </View>
      );
    }
  }, [uploading]);

  return (
    <>
      {/* 4. onPress绑定getPhotos函数,触发相册加载 */}
      <TouchableOpacity style={styles.button} onPress={getPhotos}>
        <Text style={{ color: 'white' }}>Add Photos</Text>
      </TouchableOpacity>
      {touched && error && <Text style={{ color: 'red' }}>{error}</Text>}
      {_maybeRenderUploadingOverlay()}
      {/* 可选:渲染已加载的相册图片,点击后触发上传 */}
      <View style={{ flexDirection: 'row', flexWrap: 'wrap', marginTop: 10 }}>
        {photos.map((photo, index) => (
          <TouchableOpacity 
            key={index} 
            onPress={() => _handleImagePicked({ cancelled: false, uri: photo.uri })}
            style={{ margin: 5 }}
          >
            <Image source={{ uri: photo.uri }} style={{ width: 100, height: 100, borderRadius: 8 }} />
          </TouchableOpacity>
        ))}
      </View>
    </>
  );
};

// 示例样式,可根据需求调整
const styles = {
  button: {
    backgroundColor: '#007AFF',
    padding: 12,
    borderRadius: 8,
    alignItems: 'center',
    marginVertical: 10,
  },
};

export default RenderImagePicker;

关键要点提醒

  1. Hook必须在组件顶层调用:所有useState、useCallback、自定义Hook都要放在函数组件的最外层,不能嵌套在任何函数、条件里。
  2. 事件处理函数要正确绑定:onPress需要接收一个函数,点击时执行这个函数的逻辑(比如调用getPhotos加载相册)。
  3. 状态和函数的作用域要匹配:需要访问组件状态的函数,必须定义在组件内部,或者通过依赖项传递进去(用useCallback缓存函数,避免不必要的重渲染)。
  4. 自定义Hook的返回值使用:useCameraRoll返回的photos是相册图片数组,getPhotos是加载更多图片的函数,你可以在点击按钮时调用getPhotos,然后遍历photos展示选中的图片,再绑定上传逻辑。

内容的提问来源于stack exchange,提问作者Mikk Küttim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 09:37:47