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

React Native Expo上传图片至Firebase Storage异常:仅显示白框

问题:Expo React Native 上传图片至Firebase Storage显示空白白框

我用Expo开发React Native应用,尝试将图片上传至Firebase Storage,按照官方文档实现后,上传过程无报错,但Firebase中仅显示空白白框,预览也出现错误。

上传后的Firebase显示:
Firebase中显示的空白白框

预览时的错误:
预览错误

我的完整代码:

import React, { useState } from 'react';
import { Image, View, Text, Button, ActivityIndicator } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import { app } from './firebaseConfig';
import { getStorage, ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
import 'firebase/storage'; 


const App = () => {
  const [image, setImage] = useState(null);
  const [uploading, setUploading] = useState(false);

  

  const pickImage = async () => {
    try {
      let result = await ImagePicker.launchImageLibraryAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.Images,
        allowsEditing: true,
        aspect: [4, 3],
        quality: 1,
      });
      if (!result.canceled) {
        console.log(result.assets[0].uri)
        setImage(result.assets[0].uri);
      }
    } catch (E) {
      console.log(E);
    }
  };

const storage = getStorage(app);
  // Create the file metadata
/** @type {any} */
const metadata = {
  contentType: 'image/jpeg'
};

// Upload file and metadata to the object 'images/mountains.jpg'
const storageRef = ref(storage, 'images/' + Date.now());
const uploadTask = uploadBytesResumable(storageRef, image, metadata);

console.log(storageRef)
// Listen for state changes, errors, and completion of the upload.
uploadTask.on('state_changed',
  (snapshot) => {
    // Get task progress, including the number of bytes uploaded and the total number of bytes to be uploaded
    const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
    console.log('Upload is ' + progress + '% done');
    switch (snapshot.state) {
      case 'paused':
        console.log('Upload is paused');
        break;
      case 'running':
        console.log('Upload is running');
        break;
    }
  }, 
  (error) => {
    // A full list of error codes is available at
    // https://firebase.google.com/docs/storage/web/handle-errors
    switch (error.code) {
      case 'storage/unauthorized':
        // User doesn't have permission to access the object
        break;
      case 'storage/canceled':
        // User canceled the upload
        break;

      // ...

      case 'storage/unknown':
        // Unknown error occurred, inspect error.serverResponse
        break;
    }
  }, 
  () => {
    // Upload completed successfully, now we can get the download URL
    getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
      console.log('File available at', downloadURL);
    });
  }
);

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button title="Pick an image from camera roll" onPress={pickImage} />
      {image && (
        <>
          <Image source={{ uri: image }} style={{ width: 200, height: 200 }} />
        </>
      )}
      {uploading && <ActivityIndicator />}
    </View>
  );
};

export default App;

问题原因与修复方案

核心问题

你的代码存在两个关键错误:

  1. 上传逻辑执行时机错误:上传代码直接写在组件函数体内,组件每次渲染都会自动执行上传,此时image大概率是null,导致上传了空内容到Firebase。
  2. 未处理本地文件URI:ImagePicker返回的是本地文件路径(URI),而uploadBytesResumable需要的是Blob或File对象,直接传URI无法正确读取图片数据。

修复步骤

  1. 封装上传函数,按需触发:将上传逻辑放到单独的函数中,只有在用户选择图片后再调用该函数。
  2. 转换本地URI为Blob:通过fetch获取本地文件并转换为Blob对象,再传给Firebase的上传接口。

修复后的完整代码

import React, { useState } from 'react';
import { Image, View, Button, ActivityIndicator } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
import { app } from './firebaseConfig';
import { getStorage, ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";

const App = () => {
  const [image, setImage] = useState(null);
  const [uploading, setUploading] = useState(false);

  const pickImage = async () => {
    try {
      let result = await ImagePicker.launchImageLibraryAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.Images,
        allowsEditing: true,
        aspect: [4, 3],
        quality: 1,
      });
      if (!result.canceled) {
        const selectedImageUri = result.assets[0].uri;
        setImage(selectedImageUri);
        // 选择图片后触发上传
        await uploadImage(selectedImageUri);
      }
    } catch (E) {
      console.log(E);
    }
  };

  const uploadImage = async (imageUri) => {
    setUploading(true);
    try {
      // 将本地URI转换为Blob对象
      const response = await fetch(imageUri);
      const blob = await response.blob();
      
      const storage = getStorage(app);
      const storageRef = ref(storage, 'images/' + Date.now());
      const metadata = {
        contentType: blob.type || 'image/jpeg'
      };

      const uploadTask = uploadBytesResumable(storageRef, blob, metadata);

      uploadTask.on('state_changed',
        (snapshot) => {
          const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
          console.log('Upload is ' + progress + '% done');
          switch (snapshot.state) {
            case 'paused':
              console.log('Upload is paused');
              break;
            case 'running':
              console.log('Upload is running');
              break;
          }
        },
        (error) => {
          console.error('Upload error:', error);
          setUploading(false);
          switch (error.code) {
            case 'storage/unauthorized':
              console.log('无权限访问存储对象');
              break;
            case 'storage/canceled':
              console.log('用户取消上传');
              break;
            case 'storage/unknown':
              console.log('未知错误,请检查服务端响应');
              break;
          }
        },
        () => {
          getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => {
            console.log('文件可访问地址:', downloadURL);
            setUploading(false);
          });
        }
      );
    } catch (error) {
      console.error('转换Blob或上传失败:', error);
      setUploading(false);
    }
  };

  return (
    <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
      <Button title="从相册选择图片" onPress={pickImage} />
      {image && (
        <Image source={{ uri: image }} style={{ width: 200, height: 200, marginTop: 20 }} />
      )}
      {uploading && <ActivityIndicator size="large" style={{ marginTop: 20 }} />}
    </View>
  );
};

export default App;

额外注意事项

  • Storage安全规则:开发阶段可临时设置宽松规则允许上传,正式环境需配置严格的权限控制,避免恶意上传。
  • 动态处理文件类型:代码中通过blob.type自动获取文件类型,适配JPG、PNG等不同格式的图片。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 11:35:22