expo-image
Caching, placeholders, writeToCacheAsync e carregamento progressivo - o cookbook do expo-image para feeds, galerias e miniaturas offline-first do Expo SDK 57.
Busque em todas as páginas da documentação
Caching, placeholders, writeToCacheAsync e carregamento progressivo - o cookbook do expo-image para feeds, galerias e miniaturas offline-first do Expo SDK 57.
Cartão de receita de referência rápida - pronto para copiar e colar.
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 }}
/>
);
}// Semeia o cache após expo-image-picker retornar uma 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;
}Quando usar isso:
Quando evitar:
require() - RN Image é suficiente.expo-document-picker e um visualizador de documentos.expo-image.Linha de feed com bytes progressivos, semeadura de cache a partir da captura e reciclagem segura 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" });
}O que isso demonstra:
recyclingKey={id} limpa a célula antes que a nova origem seja carregada - crítico em listas virtualizadas.onProgress controla um rótulo de porcentagem leve sem código de download personalizado.cacheKey em source emparelha com writeToCacheAsync para capturas que você já possui.Image.prefetch aquece disco + memória antes que o usuário abra uma tela de detalhes.cachePolicy | Comportamento | Usar quando |
|---|---|---|
disk (padrão) | Baixar uma vez, ler do disco depois | A maioria das miniaturas remotas |
memory-disk | Quente na RAM, volta para o disco | Avatares mostrados em muitas telas |
memory | Apenas RAM, descarregado agressivamente | Imagem principal em uma visita à tela |
none | Sempre buscar novamente | URLs assinadas que expiram rapidamente |
<Image source={{ uri: signedUrl }} cachePolicy="none" />URLs assinadas com TTL curto devem usar none ou uma chave de cache que rotaciona quando a assinatura rotaciona.
<Image
source="https://cdn.example.com/hero.webp"
placeholder={{ blurhash: post.blurhash }}
placeholderContentFit="cover"
contentFit="cover"
transition={400}
/>placeholderContentFit com contentFit - o padrão scale-down em placeholders causa cintilação.number de require() como placeholder enquanto a URL HD carrega.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 achatam para um único quadro - passe a URI do arquivo para animação sem perdas.cacheKey pulam a rede inteiramente.function onProgress(event: { loaded: number; total: number }) {
const ratio = event.total > 0 ? event.loaded / event.total : 0;
// Controle uma barra determinada ou largura de esqueleto - mantenha o trabalho O(1)
}onLoadStart / onLoad / onDisplay marcam as transições de esqueleto → carregado → pintado.priority="high" para imagens principais acima da dobra.<Image
recyclingKey={item.id}
source={{ uri: item.thumbUrl, cacheKey: `thumb:${item.id}` }}
allowDownscaling
style={{ width: 72, height: 72 }}
/>allowDownscaling (padrão true) decodifica próximo ao tamanho da visualização - deixe ativado, a menos que você precise da qualidade máxima em uma caixa minúscula.enforceEarlyResizing no iOS reduz a memória de pico para ativos de CDN superdimensionados.await Image.clearMemoryCache();
await Image.clearDiskCache();Chame a partir de uma ação Configurações → Limpar cache ou quando o usuário sair. A limpeza de disco é assíncrona e não deve ser executada a cada foco na tela.
recyclingKey ausente no FlashList - o avatar do usuário anterior pisca. Correção: recyclingKey={item.id}.placeholderContentFit="cover" combinando com contentFit.cachePolicy="none" ou rotacione cacheKey com a assinatura.Image - resizeMode mapeia para contentFit, não 1:1 para todos os valores. Correção: leia a tabela de props ao portar.writeToCacheAsync em URL remota - a API espera um arquivo local. Correção: baixe para o diretório de cache primeiro.| Alternativa | Usar quando | Não usar quando |
|---|---|---|
expo-image | Feeds remotos, caching, placeholders | Você renderiza apenas PNGs empacotados |
RN Image | Telas legadas, ícones require() | Novo desenvolvimento de feed |
react-native-fast-image | Aplicativo brownfield já padronizado nele | Greenfield Expo SDK 57 |
Skia useImage | Desenho em tela GPU | Miniaturas simples <Image> |
npx expo install expo-imageFunciona no Expo Go - nenhum plugin de configuração necessário para uso básico.
writeToCacheAsync(pickerUri, cacheKey) e depois renderize com o mesmo cacheKey.expo-image armazena em cache ativos estáticos via SDWebImage/Glide.expo-video tem uma API de cache de vídeo separada - veja expo-video.Versões da Stack: Esta página foi escrita para React 19.2.3, React Native 0.86.0 e Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última atualização: 19 de jul. de 2026