Curso react nivel medio
Optimizar una aplicación React ayuda a mejorar el rendimiento, especialmente en aplicaciones grandes o con muchas interacciones. En esta sección, aprenderemos sobre división de código (code splitting), memorización de funciones y valores, manejo de errores y, finalmente, realizaremos un ejercicio práctico para optimizar una lista grande.
1. División de código (code splitting) y lazy loading
La división de código permite cargar solo las partes de la aplicación necesarias en cada momento, en lugar de cargar todo el código de una sola vez. Esto reduce el tiempo de carga inicial.
React.lazy permite cargar componentes de forma asíncrona (lazy loading). Esto significa que el componente se carga solo cuando se necesita. React.Suspense nos permite manejar la carga de estos componentes, mostrando un mensaje o un indicador mientras se cargan.
Ejemplo de lazy loading:
1import React, { Suspense } from 'react'; 2 3// Carga perezosa del componente 4const ComponentePesado = React.lazy(() => import('./ComponentePesado')); 5 6function App() { 7 return ( 8 <div> 9 <h1>Aplicación</h1> 10 <Suspense fallback={<p>Cargando componente...</p>}> 11 <ComponentePesado /> 12 </Suspense> 13 </div> 14 ); 15} 16 17export default App;
En este ejemplo:
React.lazycarga el componenteComponentePesadosolo cuando se necesita, en lugar de cargarlo de inmediato.Suspenseenvuelve el componente y muestra “Cargando componente…” mientras el componenteComponentePesadose está cargando.
2. Memorización y optimización con useMemo y useCallback
La memorización es una técnica que ayuda a evitar cálculos innecesarios o recreaciones de funciones y valores, optimizando el rendimiento.
useMemo: Memoriza un valor o cálculo que es costoso de realizar, evitando que se recalcule en cada renderizado, a menos que cambien sus dependencias.
Ejemplo de useMemo:
1import React, { useMemo, useState } from 'react'; 2 3function App() { 4 const [numero, setNumero] = useState(0); 5 6 const calculoComplejo = useMemo(() => { 7 console.log("Realizando cálculo complejo..."); 8 return numero * 2; 9 }, [numero]); 10 11 return ( 12 <div> 13 <p>Resultado del cálculo: {calculoComplejo}</p> 14 <button onClick={() => setNumero(numero + 1)}>Incrementar</button> 15 </div> 16 ); 17} 18 19export default App;
En este ejemplo:
-
useMemomemoriza el valor decalculoComplejopara evitar recalcularlo en cada renderizado si el valor denumerono ha cambiado. -
useCallback: Memoriza una función, útil cuando la función se pasa como prop a otros componentes que podrían volver a renderizarse innecesariamente.
Ejemplo de useCallback:
1import React, { useCallback, useState } from 'react'; 2 3function Hijo({ incrementar }) { 4 return <button onClick={incrementar}>Incrementar</button>; 5} 6 7function App() { 8 const [contador, setContador] = useState(0); 9 10 const incrementar = useCallback(() => { 11 setContador((c) => c + 1); 12 }, []); 13 14 return ( 15 <div> 16 <p>Contador: {contador}</p> 17 <Hijo incrementar={incrementar} /> 18 </div> 19 ); 20} 21 22export default App;
Aquí, useCallback memoriza la función incrementar, evitando que se cree una nueva función en cada renderizado, lo que a su vez evita que el componente Hijo se renderice innecesariamente.
3. Suspense y Error Boundaries
Suspense permite manejar la carga de componentes de forma elegante, mostrando un componente de carga mientras el componente principal se carga.
Error Boundaries capturan errores en el renderizado de componentes y muestran un mensaje alternativo en lugar de hacer que toda la aplicación falle. Esto solo puede implementarse en componentes de clase.
Ejemplo de Error Boundary:
1import React, { Component } from 'react'; 2 3class ErrorBoundary extends Component { 4 constructor(props) { 5 super(props); 6 this.state = { hasError: false }; 7 } 8 9 static getDerivedStateFromError(error) { 10 return { hasError: true }; 11 } 12 13 componentDidCatch(error, info) { 14 console.error("Error capturado:", error, info); 15 } 16 17 render() { 18 if (this.state.hasError) { 19 return <h2>Algo salió mal.</h2>; 20 } 21 22 return this.props.children; 23 } 24} 25 26export default ErrorBoundary;
Este ErrorBoundary mostrará el mensaje “Algo salió mal” si ocurre un error en el renderizado de un componente hijo.
4. Ejercicio práctico: Optimización de una lista grande
Para este ejercicio, crearemos una lista muy grande de elementos y optimizaremos su renderizado usando react-window, una biblioteca que permite cargar solo los elementos visibles en pantalla.
Instalación de react-window:
1npm install react-window
Código de la aplicación optimizada (ListaGrande.js):
1import React, { useMemo, useState } from 'react'; 2import { FixedSizeList as List } from 'react-window'; 3 4const generarItems = (cantidad) => { 5 console.log("Generando lista de items..."); 6 return Array.from({ length: cantidad }, (_, index) => `Item ${index + 1}`); 7}; 8 9function ListaGrande() { 10 const [cantidad] = useState(10000); 11 12 // Memorizar la lista de items 13 const items = useMemo(() => generarItems(cantidad), [cantidad]); 14 15 return ( 16 <div> 17 <h1>Lista de {cantidad} items</h1> 18 <List 19 height={400} 20 itemCount={items.length} 21 itemSize={35} 22 width="100%" 23 > 24 {({ index, style }) => ( 25 <div style={style}>{items[index]}</div> 26 )} 27 </List> 28 </div> 29 ); 30} 31 32export default ListaGrande;
Explicación del código:
-
Memorización de items:
useMemomemoriza el array deitems, evitando recalcular la lista completa a menos que cambiecantidad.
-
Uso de
react-window:FixedSizeListdereact-windowsolo renderiza los elementos visibles en pantalla.- Propiedades:
height: Altura visible de la lista.itemCount: Número total de elementos.itemSize: Altura de cada elemento.width: Ancho de la lista.
-
Virtualización de la lista:
- Con
react-window, solo se renderizan los elementos visibles en pantalla, mejorando el rendimiento cuando la lista es muy grande.
- Con
Este ejercicio demuestra cómo optimizar el rendimiento en React usando división de código, memorización, Suspense, y Error Boundaries para crear aplicaciones eficientes y fáciles de mantener.
- Loading...