React
Tutorial React: Hooks Optimización y concurrencia
React: Aprende a optimizar componentes y gestionar actualizaciones concurrentes con hooks como memo, useMemo, useCallback, useTransition y más en React.
Aprende React GRATIS y certifícateIntroducción a la memoización y conceptos de optimización
En el contexto de React, la memoización es una técnica de optimización que consiste en almacenar el resultado de funciones costosas en términos de tiempo de cómputo, para evitar cálculos redundantes. Esta técnica es especialmente útil en aplicaciones React para mejorar la eficiencia de los componentes, reduciendo el número de renders innecesarios.
React proporciona varias herramientas para implementar memoización y optimización de rendimiento, entre las que destacan memo
, useMemo
y useCallback
. En esta sección nos centraremos en los conceptos generales de memoización y su importancia para la optimización.
memo
memo
es una función de orden superior que se utiliza para memoizar componentes funcionales. Esto significa que React solo volverá a renderizar el componente si sus propiedades (props
) han cambiado. Es útil para componentes que reciben las mismas props
frecuentemente.
// MiComponenteMemo.jsx
import { memo } from 'react';
const MiComponenteMemo = memo(function MiComponenteMemo({ valor }) {
console.log('Renderizando MiComponente');
return <div>{valor}</div>;
});
export default MiComponenteMemo;
En el ejemplo anterior, MiComponenteMemo
solo se renderizará si el valor
cambia. De lo contrario, React reutilizará el resultado de la última renderización.
useMemo
useMemo
es un hook que memoiza el resultado de una función. Se usa para evitar realizar cálculos costosos en cada renderización. useMemo
solo recalcula el valor memorizado cuando una de las dependencias ha cambiado.
// ContadorElementos.jsx
import { useMemo } from 'react';
export default function ContadorElementos({ items }) {
const itemCount = useMemo(() => {
console.log('Calculando número de elementos');
return items.length;
}, [items]);
return <div>Número de elementos: {itemCount}</div>;
}
En este ejemplo, itemCount
solo se recalculará si items
cambia, lo que puede ahorrar tiempo de cómputo en renderizaciones sucesivas.
useCallback
useCallback
es similar a useMemo
, pero se utiliza para memoizar funciones. Esto es útil cuando se pasan funciones a componentes hijos que dependen de valores que podrían cambiar en cada renderización.
// ParentComponent.jsx
import { useCallback } from 'react';
function ChildComponent({ onClick }) {
console.log('Renderizando ChildComponent');
return <button onClick={onClick}>Haz clic</button>;
}
export default function ParentComponent() {
console.log('Renderizando ParentComponent');
const handleClick = useCallback(() => {
console.log('Botón clicado');
}, []);
return <ChildComponent onClick={handleClick} />;
}
En este ejemplo, handleClick
se memoiza y no se recrea en cada renderización del ParentComponent
, evitando renderizaciones innecesarias del ChildComponent
.
Consideraciones de optimización
Es importante tener en cuenta que la memoización no siempre mejora el rendimiento y puede incluso empeorarlo si se usa en exceso o inapropiadamente. Es crucial realizar pruebas y perfiles de rendimiento para determinar si la memoización es beneficiosa en cada caso específico. Además, memo
, useMemo
y useCallback
tienen un coste adicional de memoria, por lo que deben usarse con cuidado y solo cuando realmente se justifique.
Algunos patrones comunes que se benefician de la memoización incluyen:
- Componentes que reciben grandes listas o datos complejos.
- Funciones que realizan cálculos intensivos.
- Componentes que se renderizan frecuentemente pero con las mismas propiedades.
La memoización, combinada con una arquitectura bien diseñada, puede contribuir significativamente a la eficiencia y rendimiento de las aplicaciones React.
useMemo para cálculos pesados
El hook useMemo
es una herramienta crucial para optimizar componentes en React, especialmente cuando se trata de cálculos costosos que no deberían ejecutarse en cada renderización. useMemo
memoiza el resultado de una función, recalculando solo cuando alguna de sus dependencias cambia. Esto es particularmente útil en situaciones donde el cálculo es intensivo en tiempo y recursos.
El uso de useMemo
sigue una estructura simple:
// CostesTotales.jsx
import { useMemo } from 'react';
export default function CostesTotales({ datos }) {
const resultadoCostoso = useMemo(() => {
// Supongamos que esta función realiza un cálculo intensivo
console.log('Calculando resultado costoso');
return datos.reduce((acc, item) => acc + item.valor, 0);
}, [datos]);
return <div>Resultado: {resultadoCostoso}</div>;
}
En este ejemplo, resultadoCostoso
se recalculará solo cuando datos
cambie. Esto evita que el cálculo se ejecute en cada renderización, mejorando la eficiencia del componente.
El uso de useMemo
se recomienda en los siguientes escenarios:
- Cálculos intensivos: Cuando una función realiza operaciones complejas o intensivas que no deberían ejecutarse en cada renderización.
- Filtrado y clasificación de grandes listas: Al manipular listas grandes, como filtrarlas o clasificarlas,
useMemo
puede evitar la repetición innecesaria de estas operaciones. - Transformaciones de datos: Cuando se realizan transformaciones costosas en datos que se pasan como propiedades a componentes hijos.
A continuación, se muestra un ejemplo más avanzado que incluye el filtrado y la clasificación de una lista de datos:
// ListaFiltrada.jsx
import { useMemo } from 'react';
export default function ListaFiltrada({ items, filtro }) {
const elementosFiltrados = useMemo(() => {
console.log('Filtrando elementos');
return items.filter((item) => item.name.includes(filtro)).sort();
}, [items, filtro]);
return (
<ul>
{elementosFiltrados.map((item, index) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
// App.js
import ListaFiltrada from './components/ListaFiltrada/ListaFiltrada';
export default function App() {
const products = [
{ id: 1, name: 'Product 1', valor: 10 },
{ id: 2, name: 'Product 2', valor: 20 },
{ id: 3, name: 'Product 3', valor: 30 },
{ id: 4, name: 'Product 4', valor: 40 },
]
return (
<ListaFiltrada items={products} filtro="2" />
);
}
En este ejemplo, elementosFiltrados
solo se recalcula cuando items
o filtro
cambian, optimizando así el rendimiento del componente.
Es importante tener en cuenta que useMemo
debe usarse con cuidado. Memoizar cálculos que no son realmente costosos puede llevar a un uso innecesario de memoria y a una complejidad adicional en el código. Además, useMemo
no debería ser utilizado para memoizar objetos o arrays que se pasan como propiedades a otros componentes, ya que esto puede llevar a errores difíciles de depurar debido a la referencia de igualdad.
useCallback para memoización de funciones
El hook useCallback
en React se utiliza para memoizar funciones, asegurando que no se creen nuevas instancias de estas funciones en cada renderización del componente. Esto es particularmente útil cuando se pasan funciones a componentes hijos que dependen de valores que podrían cambiar en cada renderización, evitando así renderizaciones innecesarias.
La sintaxis básica para useCallback
es la siguiente:
// MiComponente.jsx
import { useCallback } from 'react';
export default function MiComponente() {
const handleClick = useCallback(() => {
console.log('Botón clicado');
}, []);
return <button onClick={handleClick}>Haz clic</button>;
}
En este ejemplo, la función handleClick
se memoiza y no se recrea en cada renderización del componente MiComponente
. Esto es especialmente útil cuando handleClick
se pasa como una propiedad a un componente hijo.
Un caso común de uso de useCallback
es cuando se tiene un componente padre que pasa una función a un componente hijo. Sin useCallback
, la función se recrearía en cada renderización del componente padre, lo que podría causar renderizaciones innecesarias del componente hijo.
// ChildComponent.jsx
export default function ChildComponent({ onClick }) {
console.log('Renderizando ChildComponent');
return <button onClick={onClick}>Haz clic</button>;
}
// ParentComponent.jsx
import { useCallback } from 'react';
import ChildComponent from '../ChildComponent/ChildComponent';
export default function ParentComponent() {
const handleClick = useCallback(() => {
console.log('Botón clicado');
}, []);
return <ChildComponent onClick={handleClick} />;
}
En este ejemplo, handleClick
se memoiza en el componente ParentComponent
y se pasa como propiedad a ChildComponent
. Esto evita que ChildComponent
se renderice innecesariamente cada vez que ParentComponent
se renderiza, siempre y cuando handleClick
no cambie.
Es importante tener en cuenta que useCallback
acepta una lista de dependencias como segundo argumento. La función solo se volverá a crear si alguna de las dependencias cambia. Esto es crucial para asegurar que la función memoizada se comporte correctamente.
// MiComponente.jsx
import { useState, useCallback } from 'react';
export default function MiComponente() {
const [contador, setContador] = useState(0);
const incrementarContador = useCallback(() => {
setContador(prevContador => prevContador + 1);
}, []);
return (
<>
<p>Contador: {contador}</p>
<button onClick={incrementarContador}>Incrementar contador</button>
</>
)
}
En este ejemplo, incrementarContador
se memoiza y solo se vuelve a crear si alguna de las dependencias (en este caso, ninguna) cambia. Esto puede ser útil en casos donde la función depende de valores específicos del estado o de las propiedades.
Una consideración importante al usar useCallback
es importante evitar su uso excesivo. Memoizar funciones que no causan renderizaciones innecesarias puede llevar a un código más complejo y difícil de mantener sin ganancias significativas en rendimiento. Es crucial realizar perfiles de rendimiento para determinar si el uso de useCallback
es beneficioso en cada caso específico.
useTransition y useDeferredValue para actualizaciones concurrentes
En React, useTransition
y useDeferredValue
son hooks que permiten manejar actualizaciones concurrentes, mejorando la experiencia del usuario al mantener la interfaz responsiva durante operaciones costosas.
useTransition
permite definir actualizaciones de estado como transiciones, lo que ayuda a evitar bloqueos en la interfaz de usuario durante actualizaciones intensivas. Se utiliza para dividir actualizaciones urgentes y no urgentes, priorizando la respuesta de la interfaz.
// MiComponente.jsx
import { useState, useTransition } from 'react';
export default function MiComponente() {
const [isPending, startTransition] = useTransition();
const [input, setInput] = useState('');
const [list, setList] = useState([]);
const handleChange = (e) => {
setInput(e.target.value);
startTransition(() => {
const newList = Array.from({ length: 20000 }, (_, i) => `${e.target.value} ${i}`);
setList(newList);
});
};
return (
<div>
<input type="text" value={input} onChange={handleChange} />
{isPending ? <p>Cargando...</p> : <ul>{list.map((item, index) => <li key={index}>{item}</li>)}</ul>}
</div>
);
}
En este ejemplo, handleChange
utiliza startTransition
para actualizar la lista de forma concurrente. Mientras la lista se actualiza, la interfaz muestra un mensaje de carga, manteniéndose reactiva.
useDeferredValue
permite diferir la actualización de un valor hasta que las actualizaciones más urgentes se completen, útil para valores derivados de cálculos costosos.
// MiComponente.jsx
import { useState, useDeferredValue, useMemo } from 'react';
export default function MiComponente() {
const [input, setInput] = useState('');
const deferredInput = useDeferredValue(input);
const list = useMemo(() => {
return Array.from({ length: 20000 }, (_, i) => `${deferredInput} ${i}`);
}, [deferredInput]);
return (
<div>
<input type="text" value={input} onChange={(e) => setInput(e.target.value)} />
<ul>{list.map((item, index) => <li key={index}>{item}</li>)}</ul>
</div>
);
}
En este ejemplo, deferredInput
difiere la actualización de input
en la lista hasta que las actualizaciones más urgentes se completen, mejorando el rendimiento de la interfaz.
Consideraciones:
useTransition
yuseDeferredValue
son útiles para mantener la interfaz de usuario responsiva durante actualizaciones costosas.useTransition
permite dividir actualizaciones en urgentes y no urgentes, mientras queuseDeferredValue
difiere las actualizaciones de valores derivados.- Ambos hooks deben ser usados con cuidado para evitar complejidad innecesaria en el código y asegurar una experiencia de usuario óptima.
useId para generación de identificadores únicos
useId
es un hook introducido en React 18 que se utiliza para generar identificadores únicos y estables que pueden ser utilizados en elementos HTML. Esto es especialmente útil en situaciones donde se necesita un identificador único para asociar etiquetas <label>
con elementos de formulario, manejar accesibilidad, o cualquier otro caso que requiera identificadores únicos en el DOM.
La sintaxis básica para useId
es la siguiente:
import React, { useId } from 'react';
export default function MiComponente() {
const id = useId();
return (
<div>
<label htmlFor={id}>Nombre:</label>
<input id={id} type="text" name="nombre" />
</div>
);
}
En este ejemplo, useId
genera un identificador único que se utiliza tanto en el atributo htmlFor
de la etiqueta <label>
como en el atributo id
del elemento <input>
. Esto asegura que el <label>
esté correctamente asociado con el <input>
.
Una característica importante de useId
es que los identificadores generados son estables a través de renderizaciones y montajes del componente. Esto significa que el identificador no cambiará entre renderizaciones, lo cual es crucial para mantener la consistencia en aplicaciones que dependen de identificadores únicos.
useId
también se puede utilizar en componentes que se renderizan dinámicamente. Por ejemplo, en una lista de elementos de formulario generados dinámicamente:
// ListaFormularios.jsx
import { useId } from 'react';
export default function ListaFormularios({ items }) {
return (
<div>
{items.map((item, index) => {
const id = useId();
return (
<div key={index}>
<label htmlFor={id}>{item.label}</label>
<input id={id} type="text" name={item.name} />
</div>
);
})}
</div>
);
}
En este caso, cada iteración del map
genera un nuevo identificador único para cada par de <label>
y <input>
, asegurando que no haya conflictos de identificadores en el DOM.
Es importante tener en cuenta que useId
no debe ser utilizado para generar identificadores que necesiten ser persistentes entre sesiones de usuario o que deban ser compartidos a través de diferentes instancias de la aplicación. Para estos casos, es más apropiado utilizar identificadores generados en el backend o mediante alguna otra lógica de generación de identificadores persistentes.
Otra consideración es que useId
no debe ser utilizado dentro de loops o condiciones que cambien entre renderizaciones, ya que esto podría llevar a inconsistencias en los identificadores generados. En su lugar, se recomienda utilizarlo en la parte superior del componente para asegurar la estabilidad del identificador.
// FormularioCondicional.jsx
import { useId } from 'react';
export default function FormularioCondicional({ mostrarInput }) {
const id = useId(); // Generar el ID fuera de la condición
return (
<div>
{mostrarInput && (
<>
<label htmlFor={id}>Condicional:</label>
<input id={id} type="text" name="condicional" />
</>
)}
</div>
);
}
// App.js
import { useState } from 'react';
import FormularioCondicional from './components/FormularioCondicional/FormularioCondicional';
export default function App() {
const [checked, setChecked] = useState(false);
return (
<>
<label>
Checkbox:
<input type="checkbox" checked={checked} onChange={() => setChecked(!checked)} />
</label>
<FormularioCondicional mostrarInput={checked} />
</>
);
}
En este ejemplo, el identificador id
se genera una vez, independientemente de si mostrarInput
es verdadero o falso, asegurando que el identificador sea estable.
useId
es un hook útil para la generación de identificadores únicos y estables en React, asegurando la consistencia y accesibilidad en componentes que requieren identificadores únicos en el DOM. Su uso adecuado puede mejorar la calidad del código y la experiencia del usuario en aplicaciones React.
useOptimistic para UI optimista
Advertencia El Hook useOptimistic
está actualmente disponible solo en React Canary y canales experimentales, lo que significa que aún está en fase de prueba y podría cambiar antes del lanzamiento final. Si decides usarlo, asegúrate de estar al día con las actualizaciones de React y de utilizar la versión RC más reciente. Para habilitarlo, añade las versiones @rc
de react
y react-dom
con:
npm add react@rc react-dom@rc
El hook useOptimistic
en React es una herramienta valiosa para gestionar estados optimistas en la interfaz de usuario. El estado optimista permite que la interfaz reaccione inmediatamente a las acciones del usuario, asumiendo que la operación será exitosa, mientras la confirmación de la operación se procesa en segundo plano. Esto mejora la experiencia del usuario al proporcionar una respuesta inmediata y fluida.
Un caso práctico sería usar useOptimistic
en un componente de lista de tareas donde se desea añadir una tarea y actualizar la interfaz de usuario de inmediato:
// ListaTareas.jsx
import { useOptimistic, useState, useRef, useTransition } from "react";
const initialState = [
{ id: 1, titulo: "Tarea 1" },
]
export default function ListaTareas() {
const formRef = useRef(null);
const [tasks, setTasks] = useState(initialState);
const [isPending, startTransition] = useTransition();
const [optimisticTasks, setOptimisticTasks] = useOptimistic(tasks, (state, newTask) => [
...state,
newTask
]);
async function handleSubmit(e) {
e.preventDefault();
const formData = new FormData(e.target);
const titulo = formData.get("task");
if (titulo.length === 0) return; // No se puede agregar una tarea vacía
let newTask = { id: Math.random().toString(36).slice(2), titulo, isPending: true };
setOptimisticTasks(newTask);
formRef.current.reset();
//Intenta agregar la tarea a la lista de tareas, si la petición es exitosa, la añade al estado real de tareas
try {
newTask = await addTodo(newTask);
setTasks((prevTodos) => [...prevTodos, newTask]);
} catch (error) {
console.error(error);
}
}
// Simulamos una petición HTTP
async function addTodo({ titulo }) {
return new Promise((resolve, reject) => {
setTimeout(() => {
// Simulamos un error aleatorio
const error = Math.random() < 0.2; // 20% de probabilidad de error
if (error) {
reject(new Error('Error simulado'));
} else {
// Simulamos que la petición es exitosa
const data = { id: Math.random().toString(36).slice(2), titulo };
resolve(data);
}
}, 2000); // 2 segundos
});
}
return (
<>
<form onSubmit={(e) => startTransition(() => handleSubmit(e))} ref={formRef}>
<label>
Nombre de la tarea:
<input type="text" name="task" placeholder="Nueva tarea" />
</label>
<button>Añadir tarea</button>
</form>
<ul>
{optimisticTasks.map((tarea) => (
<li key={tarea.id}>{tarea.titulo} {tarea.isPending && <small>adding...</small>} </li>
))}
</ul>
</>
);
}
En este ejemplo, el componente ListaTareas
permite a los usuarios añadir tareas a una lista de manera optimista, utilizando varios hook de React como useOptimistic
, useState
, useRef
, y useTransition
.
El componente inicializa una lista de tareas (tasks
) utilizando useState
, con un estado inicial que contiene una única tarea. Se utiliza useOptimistic
para manejar el estado optimista de la lista de tareas. Esto permite que la interfaz se actualice inmediatamente cuando el usuario añade una nueva tarea, incluso antes de que la operación sea confirmada por el servidor.
El formulario de entrada se gestiona mediante una referencia (formRef
) y, al enviarse, se desencadena la función handleSubmit
. handleSubmit
utiliza useTransition
para iniciar la transición, lo que permite que la actualización de la interfaz sea más fluida y no bloquee el hilo principal.
Al enviar el formulario, se crea una nueva tarea con un estado optimista (isPending: true
), que se añade inmediatamente a la lista de tareas visibles gracias a setOptimisticTasks
. Mientras la tarea aparece en la lista, se simula una petición HTTP con la función addTodo
. Si la petición es exitosa, la tarea se confirma y se añade definitivamente al estado real (tasks
). Si la petición falla, se revierte la operación y se maneja el error en el bloque catch
, donde se podría notificar al usuario.
La función addTodo
simula una petición a un servidor con un retraso de 2 segundos. Hay un 20% de probabilidad de que la petición falle, lo que ayuda a ilustrar cómo se maneja el flujo optimista bajo condiciones de red inestables o errores de servidor.
La lista de tareas se renderiza utilizando el estado optimista. Si una tarea está pendiente de confirmación (isPending: true
), se muestra un indicador "adding..." junto a la tarea.
Consideraciones importantes al usar useOptimistic
:
- Manejo de Errores: Aunque este ejemplo maneja los errores con un bloque catch mostrando por consola un mensaje de error, es fundamental extender esta lógica para notificar al usuario de la falla.
- Optimización del Rendimiento:
useTransition
se utiliza para hacer que la experiencia del usuario sea más fluida, especialmente en situaciones donde las operaciones optimistas podrían introducir una carga adicional en la interfaz de usuario.
Ejercicios de esta lección Hooks Optimización y concurrencia
Evalúa tus conocimientos de esta lección Hooks Optimización y concurrencia con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Props y manejo de datos entre componentes
Definición y manejo de rutas
Conocimiento general de React
Galería de imágenes en React
Eventos en React
Gestor de tareas con React
Custom Hooks para servicios compartidos
Nuevos métodos create de React Router
Solicitudes HTTP con Fetch API
Instalar React y crear nuevo proyecto
Renderizado condicional
Introducción a JSX
Manejo de clases y estilos
Introducción a React Router
Solicitudes HTTP con Axios
Estado local con useState y useReducer
Estado global con Redux Toolkit
Estado y ciclo de vida de los componentes
Hooks para gestión de estado complejo y contexto
Componentes funcionales
Estado global con Context API
Hooks: optimización y concurrencia
Introducción a React y su ecosistema
Introducción a Componentes
Introducción a los Hooks
Navegación programática y redirección
Renderizado iterativo con bucles
Rutas anidadas y rutas dinámicas
Hooks: estado y efectos secundarios
Todas las lecciones de React
Accede a todas las lecciones de React y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A React Y Su Ecosistema
Introducción Y Entorno
Instalar React Y Crear Nuevo Proyecto
Introducción Y Entorno
Introducción A Jsx
Componentes
Introducción A Componentes
Componentes
Componentes Funcionales
Componentes
Eventos En React
Componentes
Props Y Manejo De Datos Entre Componentes
Componentes
Renderizado Condicional
Componentes
Renderizado Iterativo Con Bucles
Componentes
Manejo De Clases Y Estilos
Componentes
Introducción A Los Hooks
Hooks
Estado Y Ciclo De Vida De Los Componentes
Hooks
Hooks Estado Y Efectos Secundarios
Hooks
Hooks Para Gestión De Estado Complejo Y Contexto
Hooks
Hooks Optimización Y Concurrencia
Hooks
Introducción A React Router
Navegación Y Enrutamiento
Definición Y Manejo De Rutas
Navegación Y Enrutamiento
Rutas Anidadas Y Rutas Dinámicas
Navegación Y Enrutamiento
Navegación Programática Redirección
Navegación Y Enrutamiento
Nuevos Métodos Create De React Router
Navegación Y Enrutamiento
Solicitudes Http Con Fetch Api
Interacción Http Con Backend
Solicitudes Http Con Axios
Interacción Http Con Backend
Estado Local Con Usestate Y Usereducer
Servicios Y Gestión De Estado
Estado Global Con Context Api
Servicios Y Gestión De Estado
Estado Global Con Redux Toolkit
Servicios Y Gestión De Estado
Custom Hooks Para Servicios Compartidos
Servicios Y Gestión De Estado
Evaluación Test React
Evaluación
Certificados de superación de React
Supera todos los ejercicios de programación del curso de React y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender los conceptos básicos de memoización en React.
- Implementar la optimización del rendimiento utilizando
memo
,useMemo
yuseCallback
. - Gestionar actualizaciones concurrentes con
useTransition
yuseDeferredValue
. - Generar identificadores únicos y estables con
useId
. - Aplicar técnicas de UI optimista con
useOptimistic
.