下一代 Expo API:MediaLibrary 和 Contacts
SDK 55 带来了核心库的下一代版本。Shared Objects 与 Shared Refs 从根本上改变了使用这些库与原生数据交互的方式。
中文
复制

我们认为 Shared Objects 和 Shared Refs 是 Expo 库的未来,因为它们能让模块之间实现更深入的集成,而不必引入不必要的耦合。想进一步了解 shared objects 和 shared refs,可以看这篇文章。
在 SDK 54 中,我们更新了 File System,换上了新的面向对象 API。现在轮到 Contacts 和 MediaLibrary 了。更新后的版本分别以 expo-contacts/next 和 expo-media-library/next 提供。后续的 SDK 版本中,我们会继续把这一套带到其他库。
下面来看看 contacts 和 media library 这两个包具体改了什么。
MediaLibrary@Next
核心概念
新 API 围绕一个简单的想法构建:Album 和 Asset 现在是类,充当原生数据的代理。它们持有系统相册中对象的引用,方便随时获取元数据。
要创建新 asset,需要把本地 URI 传给静态函数 Asset.create()。这个调用会把文件加入相册,并返回一个 shared object:
// Create an asset from a local URI
const asset = await Asset.create('file:///path/to/photo.png');
拿到 asset 之后,管理起来就直观多了:
// Fetch an album and add the asset directly
const album = await Album.get('Holiday 2026');
await album.add(asset);
// Or create a new album and add the asset in one go
const album = await Album.create('Holiday 2026', [asset]);
直接访问属性
旧 API 常见的瓶颈在于 getAssetInfoAsync() 返回的对象体积。你只是想看一下位置,却不得不先取回一个包含所有属性的庞大普通对象。MediaLibrary@Next 提供了细粒度的 getter,让你只取需要的部分。再也不用为了看一张图片的尺寸而拉取整个元数据对象。
await asset.getLocation(); // { longitude: 50, latitude: 20 }
await asset.getShape(); // { width: 100, height: 100 }
// Need everything? You can still do that:
await asset.getInfo();
强大的查询
MediaLibrary@Next 引入了新的媒体筛选方式。新增的 Query 类借鉴了 builder 模式,让你能以可读性很高的方式构造更精确的查询。
它支持以下谓词:gt、gte、lte、lt、eq、within 和 album,另外还有用于排序的 orderBy,以及用于分页的 limit/offset。
const assets = await new Query()
.eq(AssetField.MEDIA_TYPE, MediaType.IMAGE)
.lte(AssetField.HEIGHT, 1080)
.within(AssetField.WIDTH, [920, 960, 1080])
.orderBy(AssetField.CREATION_TIME)
.limit(20)
.offset(10)
.exe();
Contacts@Next
核心概念
新 API 的设计基于以下前提:
Contacts@Next 中的 contact 对象持有系统联系人的引用,并包含大量异步函数,用于读取或更新底层系统联系人的状态。与旧 API 不同,你不必在每次函数调用时手动传入 ID,只需在特定 contact 实例上调用 update 即可,这样能避免很多容易犯的错误。
你可以用静态方法创建、获取或选取联系人,快速上手:
// Create a new contact
const contact = await Contact.create({
givenName: 'Andrew',
familyName: 'Jones',
phones: [
{
label: 'mobile',
number: '+12123456789',
},
],
});
// Fetch existing contact
const [contact] = await Contact.getAll({ limit: 1 });
// Or pick one using the system UI
const contact = await Contact.presentPicker();
细粒度更新
没人愿意为了改一个属性就去摆弄庞大的 JavaScript 对象。比如在旧版 API 里,想给某个联系人加一个新邮箱地址,你得改完整个对象再把它发回去:
// THE OLD WAY
const updatedContact = {
...contact,
emails: [...(contact.emails || []), { address: 'contacts-next@expo.dev', label: 'work' }],
};
await Contacts.updateContactAsync(updatedContact);
现在同样的操作只要一行代码:
// THE NEW WAY
await contact.addEmail({ address: 'contacts-next@expo.dev', label: 'work' });
而且每个联系人属性都能这么改!看看现在设置头像有多简单:
const result = await ImagePicker.launchImageLibraryAsync();
await contact.setImage(result.assets[0].uri);
高性能查询
查询也是一样——如果你只想取某一个特定字段,可以用细粒度的 getter:
const givenName = await contact.getGivenName();
要取多个属性,就用 getDetails。把关心的字段放进数组传进去即可:
await contact.getDetails([ContactField.EMAILS, ContactField.FULL_NAME]);
如果你在构建联系人列表,还有一个更高效的方法:getAllDetails()。它在底层只发起一次系统 API 调用,性能非常好,还支持通过可选参数做不同类型的过滤。
// This is a static module function.
await Contact.getAllDetails([ContactField.EMAILS, ContactField.FULL_NAME]);
另外,这些方法返回的对象会根据参数里请求的字段收窄类型。如果你访问了没有取回来的字段,TypeScript 会给出警告。
const details = await contact.getDetails([ContactField.GIVEN_NAME]);
console.log(details.givenName); // ✅ "John"
console.log(details.phones); // ❌ TypeScript will show a warning
.patch() + .getDetails() = <3
新版库加入了一个 patch() 方法——和 HTTP 协议里的那个类似——用来做局部修改。它会跳过值为 undefined 的属性,所以你只会更新真正想改的字段。
它的设计目标就是和 getDetails() 顺畅配合:getDetails() 只取请求的字段,patch() 只更新已定义的字段。对于只改一两个字段的表单,这再合适不过:
const details = await contact.getDetails([ContactField.EMAILS, ContactField.GIVEN_NAME]);
details.givenName = 'John';
await contact.patch(details); // Only updates the givenName in the system.
如果你想彻底替换联系人属性,仍然可以用 update 方法。和 patch 不同,它会覆盖整个联系人:
await contact.update({
givenName: 'New Name',
familyName: 'New Surname',
// Other fields will be cleared
});
看看新文档
这已经是这些库的 next 个迭代了,所以你的反馈比以往任何时候都重要。欢迎提任何意见、GitHub issue、功能请求,或者发推给我们,我们非常感激。
也欢迎你查看新文档,里面提供了 API 实际用法的示例!