Imágenes y activos
Activos estáticos, require, buckets de densidad, y carga con expo-asset.
Busca en todas las páginas de la documentación
Activos estáticos, require, buckets de densidad, y carga con expo-asset.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
import { Image, View, StyleSheet } from "react-native";
import { Asset } from "expo-asset";
// Activo local - Metro elige el bucket de densidad correcto en tiempo de compilación
const logo = require("./assets/logo.png");
// Precarga antes del primer renderizado (splash / app init)
async function preloadAssets() {
await Asset.loadAsync([
require("./assets/logo.png"),
require("./assets/hero@2x.png"),
]);
}
function Avatar({ uri }: { uri: string }) {
return (
<View style={styles.row}>
<Image source={logo} style={styles.logo} resizeMode="contain" />
<Image
source={{ uri }}
style={styles.avatar}
resizeMode="cover"
accessibilityLabel="User avatar"
/>
</View>
);
}
const styles = StyleSheet.create({
row: { flexDirection: "row", alignItems: "center", gap: 12 },
logo: { width: 120, height: 40 },
avatar: { width: 48, height: 48, borderRadius: 24 },
});Cuándo usarlo: Cualquier pantalla que muestre iconos, ilustraciones, avatares o arte de portada - activos locales vía require(), URLs remotos vía { uri }, y expo-asset cuando necesites activos listos antes de la navegación.
import { useEffect, useState } from "react";
import { ActivityIndicator, Image, StyleSheet, Text, View } from "react-native";
import { Asset } from "expo-asset";
import * as SplashScreen from "expo-splash-screen";
SplashScreen.preventAutoHideAsync();
const ASSETS = [
require("./assets/icon.png"),
require("./assets/onboarding-hero.png"),
];
type Props = { avatarUrl: string };
export default function ProfileHeader({ avatarUrl }: Props) {
const [ready, setReady] = useState(false);
useEffect(() => {
(async () => {
await Asset.loadAsync(ASSETS);
setReady(true);
await SplashScreen.hideAsync();
})();
}, []);
if (!ready) {
return (
<View style={styles.center}>
<ActivityIndicator />
</View>
);
}
return (
<View style={styles.header}>
<Image source={ASSETS[0]} style={styles.icon} resizeMode="contain" />
<Image
source={{ uri: avatarUrl }}
defaultSource={require("./assets/avatar-placeholder.png")}
style={styles.avatar}
resizeMode="cover"
onError={() => console.warn("Avatar failed to load")}
/>
<Text style={styles.title}>Your profile</Text>
</View>
);
}
const styles = StyleSheet.create({
center: { flex: 1, justifyContent: "center", alignItems: "center" },
header: { alignItems: "center", padding: 24, gap: 12 },
icon: { width: 64, height: 64 },
avatar: { width: 96, height: 96, borderRadius: 48 },
title: { fontSize: 20, fontWeight: "600" },
});Lo que esto demuestra:
require() estático y dimensiones explícitas.Asset.loadAsync() antes de ocultar la pantalla de splash.{ uri } con un placeholder defaultSource local.resizeMode para el comportamiento de escalado y onError para cargas de red fallidas.require('./assets/logo.png') en tiempo de compilación y emite un ID de activo numérico. En tiempo de ejecución, el Image de React Native resuelve ese ID al archivo correcto para la densidad de píxeles del dispositivo.@Nx: logo.png (1x), logo@2x.png, logo@3x.png. Metro elige la coincidencia más cercana; solo tienes que hacer require() del nombre base del archivo.expo-asset envuelve el mismo registro de activos pero agrega helpers de descarga/caché para URLs remotos y una API de precarga basada en promesas utilizada en el bootstrap de la aplicación.@3x pueden aumentar la RAM - dimensiona los activos para sus dimensiones de visualización, no la resolución nativa del dispositivo.| Archivo | Densidad del dispositivo | Cuándo se usa |
|---|---|---|
icon.png | ~1x (mdpi) | Android de baja densidad, alternativa |
icon@2x.png | ~2x (xhdpi) | La mayoría de iPhones, muchos teléfonos Android |
icon@3x.png | ~3x (xxhdpi) | iPhone Pro Max, Android de gama alta |
Coloca todas las variantes en el mismo directorio. Solo haz referencia al nombre base en el código:
// Metro resuelve icon@2x.png / icon@3x.png automáticamente
<Image source={require("./assets/icon.png")} style={{ width: 32, height: 32 }} />resizeMode| Modo | Comportamiento |
|---|---|
cover | Rellena el marco; recorta el desbordamiento (avatares, banners hero) |
contain | Se ajusta dentro del marco; letterbox si es necesario (logos) |
stretch | Distorsiona para rellenar (raramente deseable) |
center | Sin escalado; centra al tamaño natural |
repeat | Mosaico (solo iOS para Image; usa ImageBackground en Android) |
expo-assetimport { Asset } from "expo-asset";
import { useFonts } from "expo-font";
export async function loadAppResources() {
const imageAssets = Asset.loadAsync([
require("./assets/splash-art.png"),
require("./assets/tab-home.png"),
]);
const fontAssets = useFonts({
Inter: require("./assets/fonts/Inter-Regular.ttf"),
});
await Promise.all([imageAssets, fontAssets]);
}Llama esto en tu layout raíz o puerta de splash antes de renderizar pantallas que dependan de los activos.
import type { ImageSourcePropType, ImageStyle, StyleProp } from "react-native";
// require() devuelve un número (ID del módulo de activo) en RN
const localSource: ImageSourcePropType = require("./assets/logo.png");
// La fuente remota es un objeto
const remoteSource: ImageSourcePropType = {
uri: "https://cdn.example.com/avatar.jpg",
width: 200,
height: 200,
cache: "force-cache", // opcional: 'default' | 'reload' | 'force-cache' | 'only-if-cached'
};
type AvatarProps = {
source: ImageSourcePropType;
style?: StyleProp<ImageStyle>;
};width/height (o aspectRatio), Image se renderiza a 0×0 y parece invisible. Solución: Siempre establece layout explícito para fuentes { uri }.require() dinámicas - require('./assets/' + name + '.png') falla en tiempo de compilación porque Metro debe analizar estáticamente las importaciones. Solución: Usa un mapa de búsqueda de require estáticos o obtén URLs remotos.@3x enormes en UI pequeña - Un PNG de 3000×3000 mostrado en un avatar de 48×48 desperdicia tiempo de decodificación y RAM. Solución: Exporta al tamaño de visualización × proporción de píxeles, o usa WebP con compresión..svg en require() no funciona de forma predeterminada. Solución: Usa react-native-svg con SvgUri / SVG en línea, o exporta a PNG/WebP.Image core no anima GIFs en Android. Solución: Usa expo-image (admite formatos animados) o una librería de animación dedicada.?t=${Date.now()}) anula el caché HTTP y causa parpadeo. Solución: URIs estables; llama Image.prefetch(uri) o APIs de caché expo-image cuando necesites una actualización.await Asset.loadAsync(...) luego SplashScreen.hideAsync().| Alternativa | Úsalo cuando | No lo uses cuando |
|---|---|---|
Image core (react-native) | Imágenes locales/remotas simples, mínimas dependencias | Necesitas caché de disco, blurhash, transiciones, o soporte GIF |
expo-image | Apps de producción con muchas imágenes, listas con avatares, placeholders | Necesitas cero módulos Expo en una app RN sin prebuild |
ImageBackground | Fondos a sangre completa con niños superpuestos | Solo necesitas una imagen plana (usa Image - más ligero) |
expo-file-system + URI local | Fotos generadas por el usuario guardadas en disco | Activos agrupados estáticos (usa require()) |
react-native-fast-image (comunidad) | RN sin prebuild con necesidades agresivas de caché | Workflow administrado de Expo donde expo-image ya cubre caché |
El bundler de Metro escanea tu fuente en tiempo de compilación e incluye solo activos que puede resolver estáticamente. La construcción dinámica de rutas no puede ser analizada, por lo que el bundler lanza un error. Usa un mapa constante:
const icons = {
home: require("./assets/home.png"),
settings: require("./assets/settings.png"),
} as const;Nombra archivos con sufijos @2x y @3x junto con el archivo base. Metro selecciona la mejor coincidencia para el PixelRatio del dispositivo en tiempo de ejecución. Siempre haces require() del nombre base - nunca de la variante sufijada directamente.
Sí. React Native no infiere el tamaño del layout de los metadatos de la imagen para fuentes locales o remotas. Establece width/height en StyleSheet o usa aspectRatio con una dimensión.
defaultSource (enfocado en iOS; soporte limitado de Android) muestra una imagen agrupada mientras se carga el { uri } remoto. Para UX de placeholder multiplataforma, prefiere expo-image con props placeholder / blurhash o renderiza un View de esqueleto hasta que se dispara onLoad.
import { Image } from "react-native";
import { Asset } from "expo-asset";
// Precarga de Image core (solo remoto)
await Image.prefetch("https://cdn.example.com/hero.jpg");
// expo-asset (IDs de require locales y URLs remotos)
await Asset.loadAsync([require("./assets/card-bg.png")]);Sí. iOS y Android en las compilaciones actuales del Expo SDK decodifican WebP. WebP a menudo produce paquetes más pequeños que PNG para fotos e ilustraciones. Mantén PNG para imágenes que requieren transparencia sin pérdida en tamaños pequeños.
Las referencias de objeto source={{ uri }} inestables pueden desencadenar refetches. Memoiza el objeto de fuente o pasa un URI string estable a través de la prop source de expo-image. Evita los params de caché-busting a menos que sea intencional.
Usa useFonts de expo-font o Font.loadAsync en la misma rutina de bootstrap que Asset.loadAsync. Ambos deben completarse antes de ocultar la pantalla de splash y renderizar UI con mucho texto.
cover rellena el marco y recorta - bueno para círculos de avatar uniformes. contain se ajusta a toda la imagen dentro del marco - bueno para logos con diferentes relaciones de aspecto donde el recorte es inaceptable.
const [error, setError] = useState(false);
<Image
source={error ? require("./assets/fallback.png") : { uri }}
onError={() => setError(true)}
style={{ width: 48, height: 48 }}
/>Los proyectos de Expo convencionalmente usan una carpeta assets/ de nivel superior para iconos de aplicación e imágenes de splash (referenciadas en app.json), y carpetas ./assets/ colocadas junto a pantallas para imágenes específicas de características. Ambos funcionan - la consistencia dentro de una característica importa más que el nombre exacto de la carpeta.
Registra activos en el sistema de activos de Expo, descarga URLs remotos a un directorio de caché cuando es necesario, y garantiza que los recursos nativos estén disponibles antes de que tu JS los haga referencia. Para activos require() agrupados, principalmente garantiza la finalización de la precarga a través de promesas.
Las imágenes decorativas deben establecer accessible={false} para que los lectores de pantalla las omitan. Las imágenes significativas (avatares, gráficos) necesitan accessibilityLabel describiendo el contenido. Los iconos agrupados puramente decorativos generalmente no necesitan etiqueta.
React Native no sirve una carpeta public/ como en la web. Todas las imágenes agrupadas deben ser importadas con require() (o precargadas vía expo-asset). Las imágenes remotas usan URLs HTTPS.
ImageVersiones 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: 16 jul 2026