您可能已经到了 UI 准备好的阶段,个人资料屏幕有一个“上传照片”按钮,现在突然容易的部分变得不容易了。 实际的图像选择流程涉及本机权限、操作系统控制的界面、比许多开发者期望的要多的返回形状以及在您发布真正的构建后才会出现的构建时间细节。
Expo Image Picker 就是这样。 它是 Expo 官方库,用于打开系统 UI 选择设备库中的图像和视频或使用相机拍摄照片,如所述在 Expo 包仓库. 在实际应用中,这意味着您可以获得可靠的原生媒体输入桥梁,但不是在每个设备上都表现出相同的自定义媒体体验。
本指南是针对第一种实现而写的,而不是演示。它关注生产中重要的决策:管理型工作流设置与裸露工作流设置、不让您在后期感到惊讶的权限处理、安全的结果解析以及用户选择文件后采取的实用上传模式。 如果您正在使用自定义原生设置,则了解此处与 Expo 开发客户端工作流的区别也很有帮助。 目录.
使用 Expo 图像选择器的入门
使用 Expo 图像选择器
产品经理要求获取用户头像。 一周后,同样的功能还需要用户上传收据、拍摄事故报告的照片以及在用户第一次拒绝授权时重试。 因为图像输入涉及到原生权限、操作系统的UI、临时文件处理和后端上传流程,所以它的扩展速度很快。
expo-image-picker 这是 Expo 的 SDK 模块,负责处理图像选择。 它会打开平台选择器或相机UI,并将用户选择的媒体以一种 React Native code 可以处理的形式返回。 JavaScript API 的代码量很小。 主要的挑战在于在管理和裸机项目中正确设置原生配置、权限流程和结果处理。
主要的权衡很明显。 你让 iOS 和 Android 来呈现自己的媒体 UI,而不是在 JavaScript 中自行实现一个选择器。 这通常会得到更好的结果:用户已经理解系统屏幕,权限提示按照操作系统的预期行为,且你的团队避免了维护一个图库实现的工作。
将其视为一个原生集成功能,具有 React 接口。
这种思维方式有帮助,因为失败模式通常出现在调用选择器的按钮上。 它们通常来自以下三个地方:
- 原生配置: 缺少插件设置、错误的权限字符串或是更改配置后过时的构建
- 运行时行为: 用户可以拒绝访问、在 iOS 上授予有限的库访问权限或取消流程而不选择任何内容
- 结果解析: 当前的 API 返回一个
assetsarray, 所以更老的例子会直接失败result.uriWorkflow 选择也会改变设置路径。 在一个受管理的 Expo 应用中,native 工作的大部分都生活在应用配置中,并且当该配置发生变化时需要重新构建。在一个裸应用中,您仍然会获得 Expo 模块 __CAPGO_KEEP_0__,但您需要更直接地验证底层的 iOS 和 Android 项目设置。如果您的团队正在使用一个自定义客户端而不是 Expo Go,这个指南与 __CAPGO_KEEP_1__ 的解释相配,关于
Workflow choice also changes the setup path. In a managed Expo app, most of the native work lives in app config and requires a rebuild when that config changes. In a bare app, you still get the Expo module API, but you need to verify the underlying iOS and Android project settings more directly. If your team is using a custom client instead of Expo Go, this guide pairs well with Capgo’s explanation of 这个分离对于剩余的指南很重要,因为happy 路径只是故事的一半。 一个选择器实现是坚固的,当它在两个工作流中工作时,处理平台特定的权限怪癖而不惊吓用户,并且将一个可用的文件传递给您的上传层而不是在本地预览中停止。.
安装和基本配置
安装只需要一个命令。 得到native 配置正确是决定picker 是否在真实设备上、在自定义开发客户端上以及在您的生产构建中工作的关键。
为 React 面向的 __CAPGO_KEEP_0__ 提供了平台选择器的照片、视频和摄像头捕获。 JavaScript 调用很简单。 设置并不是,因为照片访问和摄像头访问由 iOS 和 Android 控制,而不是 React Native。
expo-image-picker 一个开发人员正在使用 Expo 图像选择器实现,通过在笔记本电脑屏幕上输入 API

使用
npx expo install expo-image-picker
而不是 expo install targetLanguage npm install 或 yarn add. Expo 与包版本匹配您的 SDK, 这避免了常见的本机兼容性问题。 如果您正在比较 Expo 模块如何适应您的发布过程,这个 Expo 工具概述 是一个有用的参考。
管理工作流程设置
在管理工作流中,声明插件在应用配置中,以便 Expo 可以在构建时间应用本机更改。
示例 app.json:
{
"expo": {
"plugins": ["expo-image-picker"]
}
}
这是最小的设置。 在实践中,团队通常还添加权限文本,尤其是在 iOS 上,系统提示应该解释为什么应用需要访问。 保持措辞具体到用户操作。 “上传个人资料照片”比“需要媒体访问权限”更好。
一个操作细节会浪费很多时间。 修改 plugins权限字符串或其他本机配置需要重新构建。 重新加载 JavaScript 不会应用这些更改。 在 Expo Go 中,您还受客户端所包含的限制。 在开发构建或生产构建中,native 项目仅在新建后才反映您的配置。
裸 React Native 设置细节
在裸应用中,包 API 与之相同,但您需要自己验证更多的本机项目。 iOS 使用说明是首先要检查的。 如果您的流程可以打开库,启动相机或录制音频视频,您的应用需要相应的权限字符串 Info.plist 在重建之前。
一个实际的检查清单对于裸项目来说是这样的:
- 安装
expo-image-picker与npx expo install expo-image-picker. - 如果您的项目使用 Expo 配置插件,请添加插件配置。
- 确认 iOS 使用说明与您暴露的功能匹配。
- 在任何本机配置更改后重建 iOS 和 Android 应用程序。
缺少权限文本通常看起来像一个运行时错误,因为 UI code 是正常的,按钮处理程序也正常。失败的位置较低。通常我会检查 Info.plist,应用程序配置,以及当前构建是否包含最新的本机更改之前再触摸组件 code。
几个习惯使设置更加可预测:
- 为实际操作写权限文本: 用户应该了解为什么他们看到提示。
- Configure camera and library separately: 在两者中,只有一个能正常工作,而另一个仍然会失败。
- Rebuild after native changes: 热重载和快速刷新不会更新本地权限。
- Test on device: 模拟器行为可能会隐藏权限和相机问题。
如果在开发期间选择器正常工作,但在 TestFlight 或 Google Play 商店发布时会崩溃,首先认为这是一个配置问题。通常情况下,这是正确的。
访问相机和媒体库
用户点击“上传照片”,期望相机或媒体库打开,而您的应用在这一刻只有一个任务:打开正确的系统 UI,处理拒绝或取消而不破坏屏幕,并返回一个可用的本地文件引用以预览或上传。
这听起来很简单,直到您测试 iOS 和 Android 的两种类型的构建(管理和裸机)。JavaScript API 会保持紧凑,但运行时行为仍然依赖于 OS 提示、设备硬件以及您之前配置的本地权限。

一个最小化但安全的组件
两种工作流程下的核心流程保持一致。请求相关权限,启动选择器,检查用户是否取消,然后读取第一个资产 result.assets.
baseline组件的外观如下:
import { useState } from 'react';
import { View, Button, Image, Alert } from 'react-native';
import * as ImagePicker from 'expo-image-picker';
export default function PhotoInput() {
const [imageUri, setImageUri] = useState<string | null>(null);
const pickFromLibrary = async () => {
const permission = await ImagePicker.requestMediaLibraryPermissionsAsync();
if (!permission.granted) {
Alert.alert('Permission required', 'Please allow photo library access.');
return;
}
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
allowsEditing: true,
quality: 1,
});
if (result.canceled) return;
const asset = result.assets?.[0];
if (!asset?.uri) return;
setImageUri(asset.uri);
};
const takePhoto = async () => {
const permission = await ImagePicker.requestCameraPermissionsAsync();
if (!permission.granted) {
Alert.alert('Permission required', 'Please allow camera access.');
return;
}
const result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
quality: 1,
});
if (result.canceled) return;
const asset = result.assets?.[0];
if (!asset?.uri) return;
setImageUri(asset.uri);
};
return (
<View>
<Button title="Choose from library" onPress={pickFromLibrary} />
<Button title="Take photo" onPress={takePhoto} />
{imageUri ? (
<Image
source={{ uri: imageUri }}
style={{ width: 200, height: 200 }}
/>
) : null}
</View>
);
}
这里有三个细节很重要
- 分别请求库和相机权限。它们独立失败
- 取消作为正常用户行为来处理,而不是错误状态
- 从
assets[0],因为选择器返回一个资产数组,而不是顶级uri.
库和相机流程
如果您想快速实现一个工作功能,首先使用库流程。它更容易测试,支持更多的模拟器设置,并避免了相机硬件边缘案例。添加相机支持后结果处理路径稳定
相机路径在开发中有更多的失败方式。iOS模拟器支持有限。Android模拟器可能不会暴露与真实设备匹配的相机行为。在bare项目中,这些差距可能会让您查看组件code,尽管实际问题是native配置或测试环境
清洁的UI模式是在调用选择器API之前要求用户提供源
const showPickerOptions = () => {
Alert.alert('Upload image', 'Choose a source', [
{ text: 'Camera', onPress: takePhoto },
{ text: 'Photo Library', onPress: pickFromLibrary },
{ text: 'Cancel', style: 'cancel' },
]);
};
这种分离使每个函数保持专注。它还使得添加分析、特性标志或后端特定规则更容易。例如,一些团队允许库上传用于个人资料图片,但要求最新的相机捕获用于身份验证
如果您的应用程序还支持Expo以外的文件访问模式,或者您正在比较本机堆栈的约定,这个 Capacitor 相册参考 对于此背景提供有用上下文。
一个短的演示在向团队成员或QA展示此流程时很有帮助:
系统UI中预期的内容
expo-image-picker 打开平台选择器或相机UI。您的应用程序不会控制该流程中的每个屏幕。这种区别很重要,因为“在我的设备上工作”通常意味着“操作系统允许我测试的路径”。
在iOS上,用户可能会授予有限的图库访问权限,而不是完全访问权限。在Android上,选择器行为可能会根据OS版本和供应商皮肤而有所不同。在受管理的工作流项目中,Expo会处理更多的本机编程工作。在裸工作流项目中,您需要确认您的构建应用程序包含您所做的本机权限更改。JavaScript调用站点在两种情况下可能相同,而运行时结果会有所不同。
我通常在调用功能完成之前测试这些案例:
- 第一次权限请求
- 拒绝权限
- 用户取消
- 成功的图库选择
- 成功捕获物理设备的摄像头
- 立即预览返回的本地 URI
这些情况直接映射到真实的生产行为。它们还设置了下一步的清洁方式,如果您需要将文件发送到服务器、审查流水线或发布端点,例如 Instagram 媒体发布 API.
处理选择器结果和选项
选择器结果是通常需要真实生产逻辑的部分。系统 UI 返回一个结构化的对象,而不是仅仅是一个文件路径,且这里的小错误会导致预览破裂、上传为空或用户取消后程序崩溃。
正确读取结果对象
当前 Expo 应用程序中关注的结果形状是 result.assets[0].uri,而不是顶级 result.uri. 这个细节会影响两种工作流程项目:管理和裸露,因为 JavaScript API 是相同的,即使原生设置在底层有所不同。
使用守卫式模式:
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
allowsEditing: true,
quality: 1,
});
if (result.canceled) {
return;
}
const asset = result.assets?.[0];
if (!asset) {
return;
}
const { uri } = asset;
setImageUri(uri);
这处理我看到的最常见的两种失败情况。取消的选择器不会给您一个可以读取的资产,且 code 会假设 result.assets[0] 始终存在的 URI 将在运行时失败。
一旦你有了 URI,预览的渲染就变得简单了:
<Image source={{ uri: imageUri }} style={{ width: 240, height: 240 }} />
如果你打算以后上传,请保留整个对象,而不是仅保留 URI。实际上, asset 和 fileName, mimeType, width, height在验证、日志记录或构建更干净的多部分请求时经常有用。 fileSize 改变下游行为的选项
几个选择器选项会影响更多的选择屏幕。它们还会影响文件大小、编辑行为以及你的后端需要接受的内容。
选项
| 类型 | 它会改变什么 | 典型用途 | always exists will fail at runtime. |
|---|---|---|---|
mediaTypes |
array | 限制用户可以选择的内容 | 限制用户只能选择图片,如果您的API只接受图片 |
allowsEditing |
boolean | 让操作系统提供裁剪或编辑UI(当支持时) | 头像、正方形封面、收据拍摄 |
quality |
number | 压缩支持的图片输出 | 减少移动网络上传大小 |
base64 |
boolean | 将编码的图片数据添加到结果中 | 仅用于明确要求内联图片数据的集成 |
一些易于忽略的权衡:
allowsEditing当图像插槽具有固定的形状或大小时,很有用。 如果您的服务器负责裁剪管道并希望保留原始文件,则其用处较小。quality会影响上传时间、内存压力和服务器存储。quality: 1并不是自动选择的正确答案。mediaTypes应与后端规则相匹配。如果服务器拒绝视频,请不要让选择器返回它们。base64会增加内存中的负载大小。除非接收服务要求这样做,否则应避免使用它。
最后一点在低内存设备上很重要。通常,用于预览和多部分上传的本地文件 URI 是更好的交付方式。 Base64 有其合理的用途,但与传递文件引用相比,它是昂贵的。
URI 与 base64
对于大多数应用程序,规则是简单的:
- 使用 URI 进行预览
- 使用 URI 用于文件上传。
- 使用 base64 只有当接收系统明确要求编码内容时才使用。
该模式使选择器code保持较小且易于测试。它还与许多后端媒体流的构建方式一致,包括最终发布到外部平台的服务,如 Instagram媒体发布API.
如果您的团队频繁发布OTA更新或移动图像密集型资产,则在此处的文件大小决策会影响整个管道。这份关于 优化应用更新的图像 的指南是选择器配置的有用补充。
一个更安全的结果模式用于真实应用
For demo code, 只存储 imageUri 在生产环境中,存储一个标准化的对象,以便下一步的预览、验证、上传或重试,不需要重新解释原始选择器响应。
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ['images'],
allowsEditing: true,
quality: 0.8,
});
if (result.canceled || !result.assets?.length) {
return;
}
const asset = result.assets[0];
setSelectedImage({
uri: asset.uri,
fileName: asset.fileName ?? 'upload.jpg',
mimeType: asset.mimeType ?? 'image/jpeg',
width: asset.width,
height: asset.height,
fileSize: asset.fileSize ?? null,
});
这给你一个可预测的形状,使应用程序 code 稳定,同时你在其他地方处理本机差异。
最后一步检查有助于。不要仅仅为了防止意外情况而启用额外的结果字段。请求你知道你需要的数据,并保持选择器专注于选择,而不是将其转换为一般的文件处理步骤。
高级模式和平台差异
一旦第一个选定的图像需要在重试、认证头、native 权限差异和真实的上传端点中存活,选择器特性就不再简单了。 expo-image-picker 处理选择很好。剩下的特性是你的应用程序的责任。

一个实用的上传模式
对于期望文件上传的 API FormData 仍然是最安全的默认设置。它在常见的 Rails、Node、Laravel、Django 和 Go 后端上都有效,并且保持了选择器与传输关注点分离。
async function uploadImage(imageUri: string) {
const formData = new FormData();
formData.append('file', {
uri: imageUri,
name: 'upload.jpg',
type: 'image/jpeg',
} as any);
const response = await fetch('https://your-api.example.com/uploads', {
method: 'POST',
body: formData,
headers: {
Accept: 'application/json',
},
});
if (!response.ok) {
throw new Error('Upload failed');
}
return response.json();
}
这 code 就足以证明路径有效,但生产应用程序通常需要一个额外的层次。派生 name 和从选定的资源中获取授权信息,尽可能在选择器函数外部附加授权信息,并保持上传状态与选择器状态分离,以便失败的请求不会迫使用户重新打开库。 type 预防常见错误的几项检查:
确认本地
- 在构建请求之前检查是否存在
uri在上传之前渲染预览,以便用户尽早发现错误的文件 - 防止在请求正在飞行时重复点击
- 将网络故障与选择器取消或权限错误分开处理
- 预期后端验证会拒绝大文件、不支持的MIME类型或缺少的授权信息
- 如果您的后端要求base64而不是multipart,那通常是一个服务器约束,而不是选择器要求。Multipart在内存上更便宜,且在移动设备上更容易理解。
在哪里平台差异实际上很重要
选择器UI是原生UI,因此它继承了原生行为。这影响了用户看到什么以及您的__CAPGO_KEEP_0__应该假设什么。
The picker UI is native, so it inherits native behavior. That affects both what users see and what your code should assume.
On iOS,编辑流程和权限提示遵循苹果的惯例。仅限照片访问可能会返回比您的测试帐户在完全授权设备上看到的更窄的资产集。 在 Android 上,选择器行为因 OS 版本和制造商皮肤而异,尤其是在相册、文件名和如何返回相机捕获方面。裸露的 React Native 应用程序更直接地感受到这些差异,因为您拥有更多的本机设置,但管理的 Expo 应用程序仍需要code,将选择器视为平台形状而不是完美统一的。
实用规则很简单。依赖于可以验证的字段,而不是跨设备的相同 UI 或相同的元数据。
几个例子在实际应用中很重要:
- 编辑和裁剪: UI 和裁剪行为在 iOS 和 Android 之间不相同
- 返回的元数据:
fileName,mimeType,fileSize可能会缺失或不一致,所以添加fallback - 权限: iOS 相片访问可以限制到选定的项目,而 Android 行为则依赖于 OS 版本和系统选择器支持
- 相机输出: 捕获的图像可能会带回不同的命名、方向或压缩特征与库资产不同
如果您的团队也在非Expo环境中工作,这个 设计Stack应用开发指南 提供了有用的Android媒体处理决策的上下文,显示在单个库之外。
管理 vs. bare 工作流程差异
在此点,设置选择开始影响操作。
在管理工作流中,权限字符串和插件配置通常存储在应用配置中,native更改在创建新构建时应用。这样可以保持JavaScript表面面积清洁,但这也意味着配置修复在下一次native构建之前不可见。OTA更新不修复缺失的native权限。
在bare工作流中,同样的功能有更多的移动部分。您需要验证native iOS使用说明,Android清单行为,包安装和重建时间本身。
在Expo和Capacitor之间切换的团队往往低估了这些抽象层次之间的差异。Capgo有一个有用的解释 关于Capacitor如何处理平台差异的,这是一个很好的比较点,如果您正在决定您的团队想要拥有的native设置量。
在两种工作流中,我都有一个偏好。保持pickercode窄,normalize结果一次,通过专门的API层上传,并将平台特定行为视为需要配置和测试的东西,而不是用假设来平滑。
常见问题的故障排除
大多数Expo图像选择器错误都属于几个小类别。最快的修复方法通常是确定哪个层次出错:配置、权限、结果处理或渲染。

快速检查常见故障
如果选择器无法打开或权限失败,请先检查原生设置。尤其是在裸应用中,缺少的iOS使用说明是常见的根源。
如果在用户关闭选择器后应用崩溃,请检查您的结果处理。许多实现仍然假设直接URI并跳过 canceled 检查。
几条快速映射帮助:
- 权限被拒绝的错误: 验证您的应用配置和原生权限字符串,然后重建。
undefined图像URI: 从result.assets?.[0]?.uri,而不是result.uri.- 点击取消后什么也没有发生: 这可能是正确的。将取消视为无操作状态。
- 图片未渲染: 确认 URI 已存储在状态中并传递到
<Image source={{ uri }} />. - 在模拟器中摄像头行为异常: 在追踪库 bug 之前,请在物理设备上进行测试。
生产检查清单(简短版)
在发布前使用此清单进行最后一次检查:
- 使用 Expo 工具进行安装: 使用
npx expo install expo-image-picker. - 配置原生组件: 添加插件和所需权限的描述。
- 主动请求权限: 分离相机和媒体库流程:
- 保护每个结果: 检查
result.canceled并安全地读取assets[0]. - 优先使用 URI 进行上传: 仅在特殊情况下保留 base64:
- 测试真实设备: 尤其是相机捕获和权限提示。
如果您的团队部署 Capacitor 或 Electron 应用程序与 React Native 项目一起, Capgo 是将 JavaScript、CSS、配置和资产更新推送到 React Native 项目而无需等待每次更改的 App Store 审核的好方法。它适用于在 web 层中存储的图像相关修复,例如上传 UI、验证规则、复制或选择器流程中的资产处理。