expo-image
Almacenamiento en caché, placeholders, writeToCacheAsync y carga progresiva - el cookbook de expo-image para Expo SDK 57 feeds, galerías y miniaturas offline-first.
Busca en todas las páginas de la documentación
Almacenamiento en caché, placeholders, writeToCacheAsync y carga progresiva - el cookbook de expo-image para Expo SDK 57 feeds, galerías y miniaturas offline-first.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
npx expo install expo-imageimport { Image } from "expo-image";
const BLURHASH =
"L6PZfSi_.AyE_3t7t7R**0o#DgR4";
export function FeedThumb({ uri }: { uri: string }) {
return (
<Image
source={{ uri }}
placeholder={{ blurhash: BLURHASH }}
placeholderContentFit="cover"
contentFit="cover"
transition={300}
cachePolicy="memory-disk"
style={{ width: "100%", aspectRatio: 1, borderRadius: 8 }}
/>
);
}// Inicializa el caché después de que expo-image-picker devuelva un URI local
import { Image } from "expo-image";
export async function cachePickedPhoto(localUri: string, assetId: string) {
const cacheKey = `upload:${assetId}`;
await Image.writeToCacheAsync(localUri, cacheKey);
return cacheKey;
}Cuándo usarlo:
Cuándo evitarlo:
require() pequeños - RN Image funciona bien.expo-document-picker y un visor de documentos.expo-image.Fila de feed con bytes progresivos, inicialización de caché desde captura y reciclaje seguro para listas.
npx expo install expo-image expo-image-picker// src/media/FeedImageRow.tsx
import { useState } from "react";
import { Image, ImageLoadEventData } from "expo-image";
import { StyleSheet, Text, View } from "react-native";
type Props = {
id: string;
uri: string;
blurhash: string;
width: number;
};
export function FeedImageRow({ id, uri, blurhash, width }: Props) {
const [progress, setProgress] = useState(0);
return (
<View style={[styles.card, { width }]}>
<Image
source={{ uri, cacheKey: `feed:${id}` }}
recyclingKey={id}
placeholder={{ blurhash }}
placeholderContentFit="cover"
contentFit="cover"
cachePolicy="memory-disk"
priority="normal"
transition={250}
onProgress={({ loaded, total }) => {
if (total > 0) setProgress(loaded / total);
}}
onLoad={() => setProgress(1)}
style={styles.image}
/>
{progress > 0 && progress < 1 ? (
<Text style={styles.progress}>{Math.round(progress * 100)}%</Text>
) : null}
</View>
);
}
const styles = StyleSheet.create({
card: { aspectRatio: 4 / 3, borderRadius: 10, overflow: "hidden", backgroundColor: "#e5e7eb" },
image: { flex: 1 },
progress: {
position: "absolute",
bottom: 8,
right: 8,
fontSize: 11,
color: "#fff",
backgroundColor: "rgba(0,0,0,0.45)",
paddingHorizontal: 6,
paddingVertical: 2,
borderRadius: 4,
},
});// src/media/prefetchFeed.ts
import { Image } from "expo-image";
export async function prefetchNextPage(urls: string[]) {
return Image.prefetch(urls, { cachePolicy: "memory-disk" });
}Lo que esto demuestra:
recyclingKey={id} limpia la celda antes de que se cargue la nueva fuente - crítico en listas virtualizadas.onProgress impulsa una etiqueta de porcentaje ligera sin código de descarga personalizado.cacheKey en source se empareja con writeToCacheAsync para capturas que ya posees.Image.prefetch calienta el disco + memoria antes de que el usuario abra una pantalla de detalles.cachePolicy | Comportamiento | Usar cuando |
|---|---|---|
disk (predeterminado) | Descargar una vez, leer desde disco después | La mayoría de miniaturas remotas |
memory-disk | En RAM, vuelve a disco | Avatares mostrados en muchas pantallas |
memory | Solo RAM, desalojado agresivamente | Imagen hero en una visita de pantalla |
none | Siempre volver a obtener | URLs firmadas que expiran rápidamente |
<Image source={{ uri: signedUrl }} cachePolicy="none" />Las URLs firmadas con TTL corto deben usar none o una clave de caché que rote cuando la firma rota.
<Image
source="https://cdn.example.com/hero.webp"
placeholder={{ blurhash: post.blurhash }}
placeholderContentFit="cover"
contentFit="cover"
transition={400}
/>placeholderContentFit con contentFit - el predeterminado scale-down en placeholders causa parpadeo.number de require() como placeholder mientras se carga la URL HD.import { Image } from "expo-image";
import * as FileSystem from "expo-file-system";
export async function cacheDownloadedFile(remoteUrl: string, stableId: string) {
const tempUri = `${FileSystem.cacheDirectory}dl-${stableId}.jpg`;
await FileSystem.downloadAsync(remoteUrl, tempUri);
const cacheKey = `asset:${stableId}`;
await Image.writeToCacheAsync(tempUri, cacheKey);
return { cacheKey, displayUri: tempUri };
}<Image source={{ uri: displayUri, cacheKey }} contentFit="cover" style={{ flex: 1 }} />ImageRef de takePictureAsync({ pictureRef: true }).ImageRef se aplana a un fotograma - pasa el URI del archivo para animación sin pérdida.cacheKey omiten la red completamente.function onProgress(event: { loaded: number; total: number }) {
const ratio = event.total > 0 ? event.loaded / event.total : 0;
// Impulsa una barra determinada o ancho de skeleton - mantén el trabajo O(1)
}onLoadStart / onLoad / onDisplay marcan las transiciones skeleton - cargado - pintado.priority="high" para imágenes hero por encima del pliegue.<Image
recyclingKey={item.id}
source={{ uri: item.thumbUrl, cacheKey: `thumb:${item.id}` }}
allowDownscaling
style={{ width: 72, height: 72 }}
/>allowDownscaling (predeterminado true) decodifica cerca del tamaño de vista - déjalo activado a menos que necesites máxima calidad en una caja pequeña.enforceEarlyResizing en iOS reduce el pico de memoria para assets CDN sobredimensionados.await Image.clearMemoryCache();
await Image.clearDiskCache();Llamar desde una acción Settings - Clear cache o cuando el usuario cierre sesión. La limpieza de disco es asincrónica y no debe ejecutarse en cada enfoque de pantalla.
recyclingKey en FlashList - el avatar del usuario anterior parpadea. Solución: recyclingKey={item.id}.placeholderContentFit="cover" coincidiendo con contentFit.cachePolicy="none" o rota cacheKey con firma.Image - resizeMode se asigna a contentFit, no 1:1 para todos los valores. Solución: lee la tabla de props al portar.writeToCacheAsync en URL remota - API espera archivo local. Solución: descarga al directorio de caché primero.| Alternativa | Usar cuando | No usar cuando |
|---|---|---|
expo-image | Feeds remotos, almacenamiento en caché, placeholders | Solo renderizas PNGs agrupados |
RN Image | Pantallas heredadas, iconos require() | Nuevo desarrollo de feed |
react-native-fast-image | App brownfield ya estandarizada en él | Greenfield Expo SDK 57 |
Skia useImage | Dibujo de lienzo GPU | Miniaturas simples de <Image> |
npx expo install expo-imageFunciona en Expo Go - no se requiere plugin de configuración para uso básico.
writeToCacheAsync(pickerUri, cacheKey) luego renderiza con la misma cacheKey.expo-image almacena en caché assets estáticos a través de SDWebImage/Glide.expo-video tiene una API de caché de video separada - ver expo-video.Versiones de Stack: Esta página fue escrita para React 19.2.3, React Native 0.86.0 y Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última actualización: 19 jul 2026