Rendimiento y calidad en Ionic: optimización, accesibilidad y depuración

Capítulo 9

Tiempo estimado de lectura: 10 minutos

+ Ejercicio

Rendimiento: medir antes de optimizar

En Ionic (Angular/React/Vue), el rendimiento tiene dos caras: real (tiempos de CPU, red, memoria) y percibido (qué tan rápido “se siente” la app). Una pantalla puede tardar 2s en estar lista, pero si muestras un esqueleto y evitas bloqueos del hilo principal, el usuario percibe fluidez.

Antes de tocar código, define una métrica simple por pantalla: tiempo hasta primer contenido útil (skeleton/placeholder), tiempo hasta interacción (botones responden) y suavidad de scroll (listas sin tirones). Luego optimiza lo que más impacta: carga diferida, listas, imágenes, estilos y re-renderizados.

Carga diferida (lazy loading) y división de código

Objetivo

Reducir el tamaño del bundle inicial y cargar pantallas/funcionalidades solo cuando se necesitan.

Guía práctica (Angular)

  • Verifica rutas lazy: cada página/feature debe estar en su módulo y cargarse con loadChildren.
  • Evita importar módulos pesados en el módulo raíz si solo se usan en una sección.
// app-routing.module.ts (ejemplo típico de lazy loading en Ionic Angular)
const routes: Routes = [
  {
    path: 'products',
    loadChildren: () => import('./products/products.module').then(m => m.ProductsPageModule)
  }
];

Guía práctica (React)

  • Usa React.lazy + Suspense para cargar vistas bajo demanda.
const Products = React.lazy(() => import('./pages/Products'));

<Suspense fallback={<IonSpinner />}>
  <Route path="/products" component={Products} exact />
</Suspense>

Guía práctica (Vue)

  • Define rutas con import dinámico.
{
  path: '/products',
  component: () => import('@/views/Products.vue')
}

Reducir re-renderizados y trabajo innecesario

Concepto

En apps híbridas, el hilo principal (UI) es un recurso crítico. Re-renderizados frecuentes, cálculos pesados en cada cambio o bindings excesivos se traducen en scroll con tirones, taps que “no entran” y animaciones poco fluidas.

Angular: OnPush y trackBy

Si un componente recibe datos inmutables (nuevas referencias al cambiar), ChangeDetectionStrategy.OnPush reduce detecciones de cambios.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

import { ChangeDetectionStrategy, Component, Input } from '@angular/core';

@Component({
  selector: 'app-product-card',
  templateUrl: './product-card.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProductCardComponent {
  @Input() product!: { id: string; name: string; price: number };
}

En listas, usa trackBy para evitar recrear DOM cuando el array cambia.

<ion-item *ngFor="let p of products; trackBy: trackById">
  {{ p.name }}
</ion-item>
trackById(_: number, item: { id: string }) {
  return item.id;
}

React: memoización y claves estables

  • Usa React.memo para componentes puros.
  • Usa useMemo/useCallback cuando pases funciones/objetos a hijos y eso dispare renders.
  • En listas, usa key estable (id), no el índice.
const ProductRow = React.memo(function ProductRow({ product, onOpen }) {
  return (
    <IonItem button onClick={() => onOpen(product.id)}>
      <IonLabel>{product.name}</IonLabel>
    </IonItem>
  );
});

const onOpen = useCallback((id) => {
  // navegación
}, []);

return products.map(p => (
  <ProductRow key={p.id} product={p} onOpen={onOpen} />
));

Vue: computed, v-memo y keys

  • Prefiere computed para derivar datos y evitar recalcular en cada render.
  • Usa :key estable en v-for.
  • En Vue 3, v-memo puede ayudar en subárboles estáticos (úsalo con criterio).
<ion-item v-for="p in products" :key="p.id">
  <ion-label>{{ p.name }}</ion-label>
</ion-item>

Optimización de listas: virtualización y patrones de UI

Concepto

Las listas largas son el caso más común de problemas de rendimiento. Renderizar cientos de nodos (items con imágenes, botones, badges) puede saturar el DOM y el layout.

Estrategias recomendadas

  • Paginación/infinite scroll: carga por bloques y evita traer todo de golpe.
  • Virtualización: renderiza solo lo visible (cuando aplique).
  • Items simples: reduce profundidad de DOM dentro de cada fila.
  • Evita cálculos en plantilla: no hagas formatPrice(p.price) en cada render si se recalcula; precalcula o memoiza.

Guía práctica: Infinite Scroll (patrón general)

Usa un tamaño de página fijo, agrega al array y marca el evento como completado. Mantén un flag hasMore para detener la carga.

// Pseudocódigo (aplicable a frameworks con pequeñas variaciones)
page = 0;
pageSize = 20;
hasMore = true;
items = [];

async function loadMore(event) {
  const next = await fetchPage(page, pageSize);
  items = items.concat(next);
  page++;
  if (next.length < pageSize) hasMore = false;
  event.target.complete();
}

Imágenes: peso, tamaño y carga eficiente

Concepto

Las imágenes suelen ser el mayor costo de red y memoria. Optimizar imágenes mejora el tiempo de carga y reduce cierres por falta de memoria en dispositivos modestos.

Checklist práctico

  • Sirve el tamaño correcto: no cargues una imagen de 2000px para mostrarla a 200px.
  • Comprime: usa formatos modernos (WebP/AVIF si tu pipeline lo permite) y calidad adecuada.
  • Lazy load: carga diferida en listas y pantallas con muchas imágenes.
  • Evita reflow: reserva espacio (ancho/alto) para que el layout no “salte”.

Ejemplo: IonImg y carga diferida

ion-img está pensado para carga eficiente y manejo de imágenes en Ionic.

<ion-img
  [src]="product.thumbnailUrl"
  alt="Foto del producto"
></ion-img>

Si usas <img>, activa lazy loading y define dimensiones.

<img
  [src]="product.thumbnailUrl"
  alt="Foto del producto"
  loading="lazy"
  width="80"
  height="80"
/>

Estilos y animaciones: menos costo de layout y pintura

Concepto

Al hacer scroll o animar, lo caro suele ser recalcular layout y repintar. En móviles, pequeños excesos de sombras, blur y layouts complejos se notan.

Buenas prácticas

  • Prefiere transform/opacity para animaciones (suelen ser más baratas que cambiar top/left/width).
  • Reduce sombras pesadas y efectos de blur en listas largas.
  • Evita selectores CSS muy costosos (profundos, con muchos descendientes) en pantallas con muchos nodos.
  • Usa variables CSS de Ionic para tematizar sin sobreescribir demasiadas reglas.
/* Ejemplo: animar con transform en lugar de top */
.card {
  transition: transform 200ms ease;
}
.card--pressed {
  transform: scale(0.98);
}

Memoización: cuándo aplica y cuándo no

Idea clave

Memoizar no es “magia”: reduce trabajo repetido a cambio de memoria y complejidad. Úsalo cuando tengas cálculos costosos o renders frecuentes por cambios no relevantes.

Casos típicos

  • Listas: formateo de datos, filtrados y ordenamientos (usar useMemo / computed).
  • Componentes puros: filas de lista que solo cambian si cambia el item.
  • Evitar recrear handlers: useCallback en React cuando se pasa a hijos memoizados.

Antipatrón común

Memoizar todo sin medir: si el cálculo es trivial, puedes empeorar rendimiento por overhead y dificultar el mantenimiento.

Accesibilidad móvil (a11y): criterios prácticos

Objetivo

Que la app sea usable con lector de pantalla, con tamaños de texto grandes, con buen contraste y con interacción táctil confiable.

Checklist esencial en Ionic

  • Etiquetas accesibles: todo control interactivo debe tener nombre (texto visible o aria-label).
  • Orden de enfoque: navegación coherente con teclado/lector (especialmente en modales).
  • Tamaño táctil: objetivos de toque cómodos (botones pequeños generan errores).
  • Contraste: texto legible sobre fondos; evita depender solo del color para indicar estado.
  • Estados: usa aria-busy, aria-live o mensajes visibles para cargas/errores.

Ejemplos

<ion-button aria-label="Abrir carrito">
  <ion-icon name="cart" aria-hidden="true"></ion-icon>
</ion-button>
<ion-item>
  <ion-label>Notificaciones</ion-label>
  <ion-toggle aria-label="Activar notificaciones"></ion-toggle>
</ion-item>

Pruebas manuales de interacción táctil

Haz una pasada rápida por pantalla con este guion:

  • Tap: ¿los botones responden a la primera? ¿hay doble tap accidental?
  • Scroll: ¿se puede hacer scroll sin activar items? (revisa button en ion-item y áreas táctiles).
  • Gestos: ¿swipe/back funciona sin conflictos con scroll horizontal?
  • Teclado: al abrir inputs, ¿el contenido queda oculto? ¿se puede enviar/cerrar sin perder contexto?
  • Rotación y tamaños de texto: ¿se rompe el layout con texto grande?

Depuración: herramientas y técnicas que sí resuelven problemas

Logs estructurados (útiles en dispositivo)

Evita console.log sueltos. Usa logs con contexto: módulo, acción, ids, duración y resultado. Esto acelera el diagnóstico de fallos intermitentes.

// Ejemplo simple de logger estructurado
function logInfo(scope: string, message: string, data?: any) {
  console.info(JSON.stringify({ level: 'info', scope, message, data, ts: Date.now() }));
}

async function loadProducts() {
  const t0 = performance.now();
  logInfo('products', 'load_start');
  try {
    const res = await fetch('/api/products');
    logInfo('products', 'load_response', { status: res.status });
  } finally {
    logInfo('products', 'load_end', { ms: Math.round(performance.now() - t0) });
  }
}

Inspección de red

Problemas típicos de “la app va lenta” son realmente de red: endpoints lentos, payloads enormes, falta de cache o demasiadas llamadas.

  • Qué revisar: tiempo total, TTFB, tamaño de respuesta, número de requests por pantalla.
  • Señales de alerta: respuestas > 1MB en listas, múltiples requests en cascada, reintentos silenciosos.
  • Acciones: paginar, comprimir, cachear, evitar duplicados (deduplicación de llamadas concurrentes).

Profiling básico (CPU y frames)

Cuando hay tirones, busca tareas largas en el hilo principal (JS) y repaints costosos.

  • Qué medir: al abrir una pantalla, al hacer scroll en lista, al abrir modal.
  • Qué suele aparecer: formateos en bucle, filtros/ordenamientos en cada render, imágenes grandes, demasiados nodos.
  • Acción rápida: mover cálculos fuera del render, memoizar derivaciones, simplificar item de lista, paginar.

Resolución de problemas típicos

Pantalla en blanco (white screen)

Suele ser un error de runtime (JS) o un problema de build/rutas. Procedimiento recomendado:

  1. Reproduce con logs: conecta el dispositivo/emulador y revisa la consola (errores rojos, stack trace).
  2. Verifica rutas: una ruta mal configurada o un import dinámico fallido puede dejar la app sin render.
  3. Revisa assets: 404 de main.js, polyfills o archivos de estilos indica problema de despliegue/base href.
  4. Desactiva temporalmente optimizaciones: source maps y build no minificado para obtener trazas legibles.
  5. Aísla el componente: comenta secciones hasta encontrar el render que rompe (por ejemplo, acceso a propiedad undefined).

Fallos de build

En proyectos Ionic, los fallos de build suelen venir de dependencias, versiones o configuración de plataforma.

  • Errores TypeScript: revisa tipos y APIs; no fuerces any para “pasar” el build si el error es real.
  • Dependencias: si aparece un conflicto, alinea versiones de paquetes relacionados (Ionic, framework, plugins).
  • Cache: limpia artefactos cuando el error es “fantasma” (builds inconsistentes).
// Comandos típicos (ajusta a tu stack)
// npm ci (instalación limpia)
// eliminar node_modules y lock si es necesario
// limpiar plataformas nativas si aplica (android/ios) y reconstruir

Problemas de permisos (cámara, ubicación, archivos)

Cuando algo funciona en navegador pero falla en dispositivo, suele ser permisos o configuración nativa.

  1. Confirma el estado del permiso: antes de ejecutar la acción, consulta si está concedido/denegado.
  2. Solicita permiso en el momento correcto: explica por qué se necesita (mejora aceptación).
  3. Revisa configuración de plataforma: en iOS, descripciones de uso; en Android, permisos en manifest y cambios por versión.
  4. Maneja denegación: muestra UI para reintentar o abrir ajustes.
// Pseudocódigo de flujo de permisos
status = await permissions.check('camera');
if (status !== 'granted') {
  status = await permissions.request('camera');
}
if (status !== 'granted') {
  showMessage('Necesitamos permiso de cámara para continuar.');
  return;
}
openCamera();

Mini plan de optimización por pantalla (paso a paso)

Úsalo como rutina repetible para cualquier vista que se sienta lenta:

  1. Define el escenario: “abrir pantalla X desde home”, “scroll hasta item 100”, “buscar y filtrar”.
  2. Instrumenta: agrega logs de inicio/fin con duración (performance.now()) y cuenta de items renderizados.
  3. Reduce red: pagina, cachea, evita duplicados, comprime payload.
  4. Optimiza lista: infinite scroll/virtualización, trackBy/key estable, item más simple.
  5. Controla renders: OnPush / memo / computed; evita crear objetos/funciones en plantilla/render.
  6. Optimiza imágenes: tamaño correcto, lazy load, reserva dimensiones.
  7. Revalida accesibilidad: labels, foco, contraste, targets táctiles.
  8. Repite medición: compara antes/después con la misma interacción.

Ahora responde el ejercicio sobre el contenido:

Si una pantalla se siente lenta en una app Ionic, ¿cuál es el enfoque recomendado para mejorar el rendimiento de forma efectiva?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

La recomendación es medir antes de optimizar: definir métricas por pantalla (primer contenido útil, tiempo hasta interacción y suavidad de scroll) y priorizar cambios con mayor impacto, en lugar de aplicar optimizaciones generales o memoizar sin evidencia.

Siguiente capítulo

Construcción y publicación de aplicaciones Ionic para Android e iOS

Arrow Right Icon
Portada de libro electrónico gratuitaIonic desde Cero: Crea Aplicaciones Híbridas con HTML, CSS y TypeScript
90%

Ionic desde Cero: Crea Aplicaciones Híbridas con HTML, CSS y TypeScript

Nuevo curso

10 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.