Commandjavascript

/optimize Command

Optimiza el rendimiento de: $ARGUMENTS

View Source

Optimizar Performance de Componente

Optimiza el rendimiento de: $ARGUMENTS

Análisis de Performance

1. Auditoría Inicial

Revisa estos aspectos en el código:

  • [ ] Re-renders innecesarios
  • [ ] Cálculos pesados sin memoización
  • [ ] Dependencias de useEffect incorrectas
  • [ ] Componentes grandes sin code splitting
  • [ ] Imágenes no optimizadas
  • [ ] Listas sin virtualización
  • [ ] CSS no optimizado
  • [ ] Peticiones API duplicadas

2. Herramientas de React

React.memo

Para componentes que re-renderean con las mismas props:

import { memo } from 'react';

const ExpensiveComponent = memo(({ data }) => {
  return <div>{/* Render pesado */}</div>;
});

// Con comparación personalizada
const MemoizedComponent = memo(
  Component,
  (prevProps, nextProps) => {
    return prevProps.id === nextProps.id;
  }
);

useMemo

Para cálculos costosos:

import { useMemo } from 'react';

const Component = ({ items }) => {
  // ❌ Malo: Se recalcula en cada render
  const sortedItems = items.sort((a, b) => a.value - b.value);

  // ✅ Bueno: Solo recalcula si items cambia
  const sortedItems = useMemo(
    () => items.sort((a, b) => a.value - b.value),
    [items]
  );

  return <List items={sortedItems} />;
};

useCallback

Para funciones que se pasan como props:

import { useCallback } from 'react';

const Parent = () => {
  // ❌ Malo: Nueva función en cada render
  const handleClick = () => {
    console.log('clicked');
  };

  // ✅ Bueno: Misma función entre renders
  const handleClick = useCallback(() => {
    console.log('clicked');
  }, []);

  return <ChildComponent onClick={handleClick} />;
};

3. Lazy Loading

Code Splitting

import { lazy, Suspense } from 'react';
import Loading from './components/Loading';

// ❌ Malo: Importa todo upfront
import HeavyComponent from './HeavyComponent';

// ✅ Bueno: Carga bajo demanda
const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <HeavyComponent />
    </Suspense>
  );
}

Lazy Images

const LazyImage = ({ src, alt }) => {
  return (
    <img
      src={src}
      alt={alt}
      loading="lazy"
      decoding="async"
    />
  );
};

4. Optimización de Listas

Virtualización

Para listas largas (> 100 items):

// Considerar usar react-window o react-virtualized
import { FixedSizeList } from 'react-window';

const VirtualList = ({ items }) => (
  <FixedSizeList
    height={600}
    itemCount={items.length}
    itemSize={50}
    width="100%"
  >
    {({ index, style }) => (
      <div style={style}>{items[index].name}</div>
    )}
  </FixedSizeList>
);

Keys Estables

// ❌ Malo: Index como key
items.map((item, index) => <Item key={index} {...item} />)

// ✅ Bueno: ID único estable
items.map(item => <Item key={item.id} {...item} />)

5. Optimización de Estado

Evitar Estado Derivado

// ❌ Malo: Estado duplicado
const [users, setUsers] = useState([]);
const [activeUsers, setActiveUsers] = useState([]);

// ✅ Bueno: Calcular on-demand
const [users, setUsers] = useState([]);
const activeUsers = users.filter(u => u.isActive);

Estado Local vs Global

// ❌ Malo: Todo en Context
const GlobalContext = createContext({
  modalOpen: false,
  buttonHover: false,
  // ...muchos estados locales
});

// ✅ Bueno: Solo estado compartido en Context
const GlobalContext = createContext({
  user: null,
  theme: 'light',
});

// Estado UI local en componente
const [modalOpen, setModalOpen] = useState(false);

6. Optimización de Imágenes

Formato y Compresión

// ✅ Usar WebP con fallback
<picture>
  <source srcSet="image.webp" type="image/webp" />
  <img src="image.jpg" alt="Description" loading="lazy" />
</picture>

// ✅ Responsive images
<img
  srcSet="
    image-small.webp 480w,
    image-medium.webp 768w,
    image-large.webp 1200w
  "
  sizes="(max-width: 768px) 100vw, 50vw"
  src="image-medium.webp"
  alt="Description"
  loading="lazy"
/>

7. Optimización de CSS

CSS Modules Optimizado

/* ❌ Evitar selectores complejos */
.container div > p:first-child span {
  color: red;
}

/* ✅ Selectores simples y específicos */
.firstParagraphText {
  color: red;
}

/* ✅ Usar transforms y opacity para animaciones */
.animated {
  transition: transform 0.2s, opacity 0.2s;
}

.animated:hover {
  transform: translateY(-2px);
  opacity: 0.9;
}

/* ❌ Evitar animaciones de propiedades pesadas */
.bad {
  transition: width 0.2s, height 0.2s, margin 0.2s;
}

8. Optimización de Peticiones

Debouncing

import { useState, useEffect } from 'react';

const SearchInput = () => {
  const [query, setQuery] = useState('');
  const [debouncedQuery, setDebouncedQuery] = useState('');

  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedQuery(query);
    }, 300);

    return () => clearTimeout(timer);
  }, [query]);

  useEffect(() => {
    if (debouncedQuery) {
      // Hacer búsqueda
      fetchResults(debouncedQuery);
    }
  }, [debouncedQuery]);

  return (
    <input
      value={query}
      onChange={(e) => setQuery(e.target.value)}
    />
  );
};

Cache de Peticiones

const cache = new Map();

const fetchWithCache = async (url) => {
  if (cache.has(url)) {
    return cache.get(url);
  }

  const response = await fetch(url);
  const data = await response.json();
  cache.set(url, data);
  return data;
};

9. useEffect Optimization

// ❌ Malo: Dependencias faltantes
useEffect(() => {
  fetchData(userId);
}, []); // ESLint warning

// ✅ Bueno: Dependencias correctas
useEffect(() => {
  fetchData(userId);
}, [userId]);

// ✅ Bueno: Cleanup
useEffect(() => {
  const controller = new AbortController();

  fetchData(userId, { signal: controller.signal });

  return () => controller.abort();
}, [userId]);

Métricas de Performance

Antes de Optimizar

  • Time to Interactive: ___ms
  • First Contentful Paint: ___ms
  • Largest Contentful Paint: ___ms
  • Re-renders por segundo: ___
  • Bundle size: ___KB

Después de Optimizar

  • Time to Interactive: ___ms (mejora: ___%)
  • First Contentful Paint: ___ms (mejora: ___%)
  • Largest Contentful Paint: ___ms (mejora: ___%)
  • Re-renders por segundo: ___ (mejora: ___%)
  • Bundle size: ___KB (reducción: ___%)

Checklist de Optimización

React Específico

  • [ ] Componentes pesados envueltos en React.memo
  • [ ] Cálculos costosos con useMemo
  • [ ] Callbacks con useCallback
  • [ ] Code splitting con lazy y Suspense
  • [ ] Keys estables en listas
  • [ ] Estado local en lugar de global donde sea posible

Assets

  • [ ] Imágenes en WebP
  • [ ] Imágenes con loading="lazy"
  • [ ] Sprites o SVG inline para iconos
  • [ ] Fuentes optimizadas (woff2)

Red

  • [ ] Debouncing en inputs de búsqueda
  • [ ] Cache de peticiones frecuentes
  • [ ] AbortController para cleanup
  • [ ] Parallel loading donde sea posible

CSS

  • [ ] Animaciones usando transform/opacity
  • [ ] Selectores simples
  • [ ] CSS crítico inline (si aplica)
  • [ ] Eliminar CSS no usado

Bundle

  • [ ] Tree shaking habilitado
  • [ ] Dependencias pesadas lazy loaded
  • [ ] Análisis de bundle (vite-bundle-visualizer)

Herramientas de Medición

# Analizar bundle
npm run build
npx vite-bundle-visualizer

# React DevTools Profiler
# Usar en navegador para medir re-renders

# Lighthouse
# Correr en Chrome DevTools

Objetivos

  • 🎯 First Paint < 1s
  • 🎯 Interactive < 3s
  • 🎯 Re-renders mínimos (< 5 por interacción)
  • 🎯 Bundle size < 200KB (gzipped)
  • 🎯 Lighthouse Score > 90

Notas

  • No optimices prematuramente - mide primero
  • Prioriza UX sobre micro-optimizaciones
  • El código más rápido es el que no se ejecuta
  • Considera el tradeoff complejidad vs ganancia