您可能已经到了UI准备好的阶段,配置文件屏幕有一个“上传照片”按钮,现在容易的部分突然变得不容易了。 实际的图像选择流程涉及本机权限、操作系统控制的界面、与许多开发人员期望的不同返回形状、以及在您部署一个真正的构建后才会出现的构建时详细信息。
expo图像选择器正是您需要的。 它是expo官方图像选择器库,用于打开系统UI选择设备库中的图像和视频或使用相机拍摄照片,正如在 expo包仓库中描述的那样 . 在实际应用中,这意味着您可以获得可靠的原生媒体输入桥接,但不是每个设备上都表现一致的自定义媒体体验。
本指南是针对第一版实现而写的,而不是演示。它关注的是在生产环境中重要的决策:管理型工作流设置与裸露型工作流设置、不让您在后期感到惊讶的权限处理、安全的结果解析以及在用户选择文件后进行实用的上传模式。如果您正在使用自定义原生设置,了解本指南与Expo开发客户端工作流的区别也很有帮助。 Expo开发客户端工作流.
目录
开始使用Expo图像选择器
产品经理要求添加用户头像功能。 一周后,同样的功能也需要添加收据上传、事件报告拍摄摄像头和用户第一次拒绝权限时重试功能。图像输入功能快速扩展,因为它涉及原生权限、操作系统拥有的UI、临时文件处理和后端上传流程。
expo-image-picker 是Expo的SDK模块,负责此项工作。它打开平台选择器或摄像头UI,并返回选择的媒体以便React Nativecode处理。JavaScriptAPI非常小。主要挑战在于在管理和裸机项目中正确设置原生配置、权限流程和结果处理。
主要的权衡是明显的。您让iOS和Android呈现自己的媒体UI,而不是在JavaScript中构建自定义选择器。这通常会给出更好的结果:用户已经理解系统屏幕,权限提示按照操作系统的期望行为,而您的团队避免了在JavaScript中维护画廊实现。
将其视为原生集成功能,具有React界面。
这种思维方式有帮助,因为失败模式通常出现在调用选择器的按钮中。它们通常来自以下三个地方:
- 原生配置: 缺少插件设置、错误的权限字符串或更改配置后过时的构建
- 运行时行为: 用户可以拒绝访问、在iOS上授予有限的库访问权限或取消流程而不选择任何内容
- 结果解析: 当前API返回一个
assetsarray, so older examples that readresult.uri直接失败
工作流选择也会改变设置路径。 在一个受管理的 Expo 应用中,大部分本机工作都生活在应用配置中,并且当该配置发生变化时需要重新构建。在一个裸应用中,您仍然可以获得 Expo 模块 API, 但您需要更直接地验证底层 iOS 和 Android 项目设置。如果您的团队正在使用一个自定义客户端而不是 Expo Go,则本指南与 Capgo 的解释相配,后者解释了 Expo 开发客户端如何改变本机模块测试 这两个工作流程的区别对于本指南的其余部分至关重要。 一个选择器实现是坚固的,当它在两个工作流程中都工作时,处理平台特定的权限奇怪时不会惊讶用户,并且将可用的文件传递给上传层而不是停止在本地预览时。 .
安装和基本配置
安装只需要一个命令。 确保本机配置正确是决定选择器在真实设备、自定义开发客户端和生产构建中是否工作的关键因素。
为 React 提供了一个 __CAPGO_KEEP_0__,覆盖了平台选择器的照片、视频和摄像头捕获。 JavaScript 调用简单。 设置不是,因为照片访问和摄像头访问由 iOS 和 Android 控制,而不是 React Native。
expo-image-picker 开发人员正在使用 Expo 图像选择器实现的 laptop 电脑屏幕上输入 API。

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

一个最小但安全的组件
两种 Expo 管理和裸露工作流项目的核心流程保持一致。 请求相关权限,启动选择器,检查用户是否取消,然后读取第一个资产 result.assets.
基线组件的外观如下:
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 模拟器可能不会暴露与真实设备匹配的相机行为。 在裸露项目中,这些差距可能会让你查看组件 code,尽管实际问题是本机配置或测试环境。
A clean UI pattern is to ask the user for the source before calling the picker 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更新或移动图像密集型资产,则在此处的文件大小决策会影响整个管道。有关 优化应用程序更新的图像 的指南是选择器配置的有用补充。
一个更安全的结果模式
为了演示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保持稳定,同时你在其他地方处理原生差异。
最后一步检查有助于。不要仅仅为了万一而启用额外的结果字段。请求你知道需要的数据,保持选择器专注于选择,而不是将其转换为一般的文件处理步骤。
高级模式和平台差异
一旦第一个选中的图片需要在重试、认证头、原生权限差异和真实上传端点下存活,选择器特性就会停止简单化。 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而不是多部分,那通常是一个服务器约束,而不是选择器要求。多部分在内存上更便宜,且在移动设备上更容易理解。
在哪里平台差异实际上很重要
选择器UI是本地的,所以它继承了本地行为。影响用户看到什么以及您的code应该假设什么。
On iOS,编辑流程和权限提示遵循苹果的惯例。仅限照片访问可能会返回比测试帐户在完全授权设备上看到的资产数量更少的资产。 在 Android 上,选择器行为会根据 OS 版本和制造商皮肤而有所不同,尤其是在专辑、文件名和如何返回相机捕获方面。bare React Native 应用程序会更直接地感受到这些差异,因为您拥有更多的本机设置,但管理的 Expo 应用程序仍需要code,将选择器视为平台形状而不是完美统一的。
实践规则很简单。依赖于可以验证的字段,而不是依赖于设备之间相同的 UI 或相同的元数据。
几个例子在真实应用中很重要:
- 编辑和裁剪: UI 和裁剪行为在 iOS 和 Android 之间不相同
- 返回的元数据:
fileName,mimeType,并且fileSize可能会缺失或不一致,所以添加fallbacks - 权限: iOS 相册访问可能仅限于选定的项目,而 Android 行为则依赖于 OS 版本和系统选择器支持
- 相机输出: 捕获的图像可能会带回不同的命名、方向或压缩特征与库资产
如果您的团队也在Expo之外工作,这个 设计栈应用开发指南 为媒体处理决策提供了有用的Android背景
管理 vs. bare 工作流程差异
在此点,设置选择开始变得操作性
在管理工作流中,权限字符串和插件配置通常存储在应用配置中,而原生更改则在创建新构建时应用。这样可以保持JavaScript表面面积清洁,但这也意味着配置修复在下一次原生构建之前不可见。OTA更新不修复缺失的原生权限。
在bare工作流中,同样的功能有更多的移动部分。您需要验证原生iOS使用说明、Android清单行为、包安装和重建时间。这种方式的好处是控制权。然而,这也意味着一个选择器问题可能是由原生配置而不是JavaScript调用站点引起的。
那些在Expo和Capacitor之间切换的团队经常低估了这些抽象层之间的差异。Capgo对Capacitor如何处理平台差异有一个有用的解释, how Capacitor handles platform differences我的偏好在两种工作流中都是一致的。保持选择器__CAPGO_KEEP_0__的范围狭窄,normalize结果一次,通过一个专门的__CAPGO_KEEP_1__层上传,并将平台特定行为视为需要配置和测试的内容,而不是用假设来平滑过。
My preference is consistent across both workflows. Keep picker code narrow, normalize the result once, upload through a dedicated API layer, and treat platform-specific behavior as something to configure and test explicitly rather than smooth over with assumptions.
Troubleshooting Common Issues
Expo 图像选择器 bug 大多数都属于几个小类别。最快的修复方法通常是确定哪个层次出错:配置、权限、结果处理或渲染。

快速检查常见故障。
如果选择器无法打开或权限失败,请先检查原生设置。尤其是在裸壳应用中,缺少 iOS 使用说明是常见的根源。
如果应用在用户关闭选择器后崩溃,请检查结果处理。许多实现仍然假设直接 URI 并跳过检查。 canceled 快速映射帮助:
权限拒绝错误:
- 验证您的应用配置和原生权限字符串,然后重建。 图像 URI:
undefined从 ,而不是result.assets?.[0]?.uri__CAPGO_KEEP_0__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 在 Capgo 上提交 PR 时,请注意:Capgo 是一种将 JavaScript、CSS、配置和资产更新直接部署到移动端应用程序的方式,无需等待 App Store 的审核过程。它适用于在 web 层中修复图片相关问题,例如上传 UI、验证规则、复制或资产处理等。