您可能已经准备好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 开发人员正在使用API在笔记本电脑屏幕上实现Expo图像选择器。

使用
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__ 与此相同,但您需要自己验证本机项目。 iOS 使用说明是首先要检查的。如果您的流程可以打开库,启动摄像头或录制音频视频,则您的应用需要在
In a bare app, the package API is the same, but you need to verify more of the native project yourself. iOS usage descriptions are the first thing to check. If your flow can open the library, launch the camera, or record video with audio, your app needs the corresponding permission strings in Info.plist 在重建之前。
一个实用的检查清单对于裸项目来说如下:
- 安装
expo-image-picker使用npx expo install expo-image-picker. - 如果您的项目使用 Expo 配置插件,则添加插件配置。
- 确认 iOS 使用说明与您暴露的功能相匹配。
- 在任何本机配置更改后重建 iOS 和 Android 应用程序。
缺少权限文本通常看起来像一个运行时错误,因为 UI code 是正常的,按钮处理程序也正常。失败的位置较低。通常我会检查 Info.plist,应用程序配置,以及当前构建是否包含最新的本机更改之前,我会触摸组件 code。
几个习惯可以使设置更加可预测:
- 为实际操作写权限文本: 用户应该了解为什么他们看到提示。
- 配置相机和库单独设置: 一个可以工作,而另一个仍然失败。
- 重建后原生更改: 热重载和快速刷新不会更新原生权限。
- 在设备上测试: 模拟器行为可以隐藏权限和相机问题。
如果在开发期间选择器正常工作,但在 TestFlight 或 Play Store 构建中出现问题,请先考虑配置问题。通常情况下,这是正确的。
访问相机和媒体库
用户点击“上传照片”,期望相机或库打开,应用程序在那一刻有一个任务。打开正确的系统 UI,处理拒绝或取消而不破坏屏幕,并返回可用的本地文件引用以预览或上传。
这听起来很简单,直到测试 iOS 和 Android 的管理和裸机构建。JavaScript API 保持紧凑,但运行时行为仍然依赖于 OS 提示、设备硬件以及您之前配置的原生权限。

一个最小但安全的组件
两种 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 展示此流程:
expo-image-picker 从系统 UI 中期望什么
打开平台选择器或相机 UI。您的应用程序不会控制该流程中的每个屏幕。这种区别很重要,因为“在我的设备上工作”通常意味着“操作系统允许我测试的路径”。
在 iOS 上,用户可能会授予有限的库访问权限,而不是完全访问权限。在 Android 上,选择器行为可能会根据 OS 版本和供应商皮肤而有所不同。在管理工作流项目中,Expo 会为您处理更多的原生编程。 在裸工作流项目中,您需要确认您的构建应用程序包含您所做的原生权限更改。 JavaScript 调用站点在两种情况下可能相同,而运行时结果会有所不同。
- 我通常在调用功能完成之前测试这些案例:
- 第一次权限请求
- 被拒绝的权限
- 用户取消
- 在物理设备上成功捕获相机
- 立即预览返回的本地 URI
这些情况直接映射到真实的生产行为。它们还设置了下一步,如果需要将文件发送到服务器、审查流水线或发布端点(例如 Instagram 媒体发布 __CAPGO_KEEP_0__) 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 改变下游行为的选项
几个选择器选项会影响更多的选择屏幕。它们影响文件大小、编辑行为和你的后端需要接受的内容。
选项
| 类型 | 它会改变什么 | 典型用途 | object |
|---|---|---|---|
mediaTypes |
数组 | 限制用户可以选择的内容 | 如果您的API仅支持图片,请限制用户选择图片 |
allowsEditing |
布尔值 | 让操作系统提供裁剪或编辑UI(在支持的设备上) | 头像、正方形封面、收据截图 |
quality |
数字 | 压缩支持的图片输出 | 减少上传大小以适应移动网络 |
base64 |
布尔值 | 将编码的图片数据添加到结果中 | 仅用于明确要求内联图片数据的集成 |
A few trade-offs are easy to miss:
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 稳定,方便管理和裸露项目保持一致。
最后一步检查有助于。不要仅仅为了万一而启用额外的结果字段。请求你知道需要的数据,保持选择器专注于选择,而不是将其转换为一般的文件处理步骤。
高级模式和平台差异
一旦第一个选定的图片需要在重试、认证头、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而不是多部分,那通常是一个服务器约束,而不是选择器要求。多部分在内存上更便宜,且在移动设备上更容易理解。
在哪里平台差异实际上很重要
选择器UI是本机的,因此它继承了本机行为。影响用户看到的内容以及您的code应该假设的内容。
On iOS,编辑流程和权限提示遵循 Apple 的惯例。仅限 Photos 访问可能会返回比您的测试帐户在完全授权设备上看到的资产数量更少的资产。 在 Android 上,选择器行为会根据 OS 版本和制造商皮肤而有所不同,尤其是在相册、文件名和如何返回相机捕获方面。bare React Native 应用程序会更直接地感受到这些差异,因为您拥有更多的本机设置,但管理的 Expo 应用程序仍需要code,将选择器视为平台形状而不是完美统一的。
实践规则很简单。依赖于可以验证的字段,而不是依赖于设备之间相同的 UI 或相同的元数据。
几个例子在真实应用中很重要:
- 编辑和裁剪: UI 和裁剪行为在 iOS 和 Android 之间不相同
- 返回的元数据:
fileName,mimeType,并且fileSize可能会缺失或不一致,所以添加fallbacks - 权限: iOS 相册访问可能仅限于选定的项目,而 Android 行为则依赖于 OS 版本和系统选择器支持
- 相机输出: 捕获的图像可能会带有不同的命名、方向或压缩特征与库资产
如果您的团队也在Expo之外工作,这个 设计栈应用开发指南 为媒体处理决策提供了有用的Android背景,展示了超出单个库的内容。
管理 vs. bare 工作流程差异
在此阶段,设置选择开始影响操作。
在管理工作流中,权限字符串和插件配置通常存储在应用配置中,而native更改在创建新构建时应用。这样可以保持JavaScript表面面积清洁,但这也意味着配置修复在下一次native构建之前不可见。OTA更新不修复缺失的native权限。
在bare工作流中,相同的功能有更多的移动部分。您需要验证native iOS使用说明,Android清单行为,包安装和重建时间本身。这种方式的好处是控制。然而,这种方式的成本是,选择器问题可能由native配置引起,而不是由JavaScript调用站点引起。
Teams that switch between Expo and Capacitor often underestimate how different these abstraction layers are. Capgo has a useful explanation of how Capacitor handles platform differences,这是一个很好的比较点,如果您正在决定您的团队想要拥有的native设置量。
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.
常见问题的故障排除
大多数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 在 Capgo 上提交 PR 时,Capgo 是一个可供选择的选项,用于将 JavaScript、CSS、配置和资产更新交付给客户端,而无需等待每次更改的商店审核。它与图像相关的修复位于您的 Web 层时相关,例如上传 UI、验证规则、复制或资产处理等。