Optimización de rendimiento en React: Técnicas avanzadas
El rendimiento es crucial en las aplicaciones web modernas. Una aplicación lenta puede resultar en una mala experiencia de usuario y pérdida de conversiones. En este artículo, exploraremos técnicas avanzadas para optimizar aplicaciones React.
1. React.memo y useMemo
React.memo
Evita re-renderizados innecesarios de componentes:
const ExpensiveComponent = React.memo(({ data, onClick }) => {
console.log('Renderizando componente costoso')
return (
<div>
{data.map(item => (
<div key={item.id} onClick={() => onClick(item)}>
{item.name}
</div>
))}
</div>
)
}, (prevProps, nextProps) => {
// Comparación personalizada
return prevProps.data.length === nextProps.data.length
})
useMemo
Memoriza cálculos costosos:
function DataProcessor({ items, filter }) {
const processedData = useMemo(() => {
console.log('Procesando datos...')
return items
.filter(item => item.category === filter)
.map(item => ({
...item,
processed: heavyComputation(item)
}))
}, [items, filter])
return <DataList data={processedData} />
}
2. useCallback para funciones estables
Previene la recreación de funciones en cada render:
function TodoList({ todos, onToggle }) {
const handleToggle = useCallback((id) => {
onToggle(id)
}, [onToggle])
return todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={handleToggle}
/>
))
}
3. Code Splitting y Lazy Loading
Lazy Loading de componentes
const HeavyChart = lazy(() => import('./components/HeavyChart'))
function Dashboard() {
const [showChart, setShowChart] = useState(false)
return (
<div>
<button onClick={() => setShowChart(true)}>
Mostrar gráfico
</button>
{showChart && (
<Suspense fallback={<div>Cargando gráfico...</div>}>
<HeavyChart />
</Suspense>
)}
</div>
)
}
Route-based Code Splitting
const routes = [
{
path: '/',
component: lazy(() => import('./pages/Home'))
},
{
path: '/dashboard',
component: lazy(() => import('./pages/Dashboard'))
},
{
path: '/profile',
component: lazy(() => import('./pages/Profile'))
}
]
4. Virtualización de listas
Para listas largas, usa react-window o react-virtualized:
import { FixedSizeList } from 'react-window'
function VirtualList({ items }) {
const Row = ({ index, style }) => (
<div style={style}>
{items[index].name}
</div>
)
return (
<FixedSizeList
height={600}
itemCount={items.length}
itemSize={50}
width="100%"
>
{Row}
</FixedSizeList>
)
}
5. Optimización de imágenes
Lazy loading de imágenes
function OptimizedImage({ src, alt, ...props }) {
const [imageSrc, setImageSrc] = useState(null)
const imageRef = useRef()
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setImageSrc(src)
observer.disconnect()
}
},
{ threshold: 0.1 }
)
if (imageRef.current) {
observer.observe(imageRef.current)
}
return () => observer.disconnect()
}, [src])
return (
<img
ref={imageRef}
src={imageSrc || '/placeholder.jpg'}
alt={alt}
{...props}
/>
)
}
6. Debounce y Throttle
Debounce para inputs de búsqueda
function SearchInput({ onSearch }) {
const [query, setQuery] = useState('')
const debouncedSearch = useMemo(
() => debounce((value) => {
onSearch(value)
}, 300),
[onSearch]
)
const handleChange = (e) => {
const value = e.target.value
setQuery(value)
debouncedSearch(value)
}
return (
<input
type="text"
value={query}
onChange={handleChange}
placeholder="Buscar..."
/>
)
}
7. Context optimization
Divide contextos grandes para evitar re-renders innecesarios:
// En lugar de un contexto grande
const AppContext = createContext()
// Usa múltiples contextos específicos
const UserContext = createContext()
const ThemeContext = createContext()
const SettingsContext = createContext()
// O usa un patrón de selector
function useAppContext(selector) {
const context = useContext(AppContext)
return useMemo(() => selector(context), [context, selector])
}
Herramientas de análisis
React DevTools Profiler
Usa el Profiler para identificar componentes lentos:
<Profiler id="Navigation" onRender={onRenderCallback}>
<Navigation />
</Profiler>
Web Vitals
Monitorea métricas clave:
import { getCLS, getFID, getFCP, getLCP, getTTFB } from 'web-vitals'
function sendToAnalytics(metric) {
// Envía métricas a tu servicio de analytics
console.log(metric)
}
getCLS(sendToAnalytics)
getFID(sendToAnalytics)
getFCP(sendToAnalytics)
getLCP(sendToAnalytics)
getTTFB(sendToAnalytics)
Conclusión
La optimización del rendimiento en React es un proceso continuo. No todas las optimizaciones son necesarias para cada aplicación. Mide primero, identifica cuellos de botella reales, y luego aplica las técnicas apropiadas.
Recuerda: la optimización prematura es la raíz de todos los males. Enfócate primero en escribir código limpio y mantenible, luego optimiza donde sea necesario.