useMemo
useMemo
es un Hook de React que te permite guardar en caché el resultado de un cálculo entre renderizados.
const cachedValue = useMemo(calculateValue, dependencies)
- Referencia
- Uso
- Solución de problemas
- Mi cálculo se ejecuta dos veces en cada renderizado
- Se supone que mi llamada a
useMemo
devuelve un objeto, pero devuelve undefined - Cada vez que mi componente se renderiza, el cálculo en
useMemo
vuelve a ejecutarse - Necesito llamar a
useMemo
para cada elemento de la lista en un bucle, pero no está permitido
Referencia
useMemo(calcularValor, dependencias)
Llama a useMemo
en el nivel superior de tu componente para guardar en caché un cálculo entre rerenderizados:
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}
Parámetros
-
calcularValor
: La función que calcula el valor que deseas memoizar. Debe ser pura, no debe aceptar argumentos y debe devolver un valor de cualquier tipo. React llamará a tu función durante el renderizado inicial. En renderizados posteriores, React devolverá el mismo valor nuevamente si lasdependencias
no han cambiado desde el última renderizado. De lo contrario, llamará acalcularValor
, devolverá su resultado y lo almacenará en caso de que pueda reutilizarse más tarde. -
dependencias
: La lista de todos los valores reactivos a los que se hace referencia dentro del códigocalcularValor
. Los valores reactivos incluyen props, estado y todas las variables y funciones declaradas directamente dentro del cuerpo de tu componente. Si tu linter está configurado para React, verificará que cada valor reactivo esté correctamente especificado como una dependencia. La lista de dependencias debe tener un número constante de elementos y escribirse en línea como[dep1, dep2, dep3]
. React comparará cada dependencia con su valor anterior usando el algoritmo de comparaciónObject.is
.
Devuelve
En el renderizado inicial, useMemo
devuelve el resultado de llamar a calcularValor
sin argumentos.
Durante los renderizados posteriores, devolverá un valor ya almacenado del último renderizado (si las dependencias no han cambiado), o llamará a calcularValor
nuevamente y devolverá el resultado que calcularValor
haya devuelto.
Advertencias
useMemo
es un Hook, por lo que solo puede llamarse en el nivel superior del componente o sus propios Hooks. No puedes llamarlo dentro de bucles o condiciones. Si lo necesitas, extrae un nuevo componente y mueve el estado a él.- En modo estricto, React llamará a tu función de cálculo dos veces para ayudarte a encontrar impurezas accidentales. Este comportamiento ocurre solo en desarrollo y no afecta a producción. Si tu función de cálculo es pura (como debería ser), esto no debería afectar la lógica de tu componente. Se ignorará el resultado de una de las llamadas.
- React no descartará el valor almacenado en caché a menos que haya una razón específica para hacerlo. Por ejemplo, en desarrollo, React descartará la caché cuando edites el archivo de tu componente. Tanto en desarrollo como en producción, React desechará la caché si tu componente se suspende durante el montaje inicial. En el futuro, React puede agregar más funcionalidades que hagan descartar la caché; por ejemplo, si React incorporara una funcionalidad para listas virtualizadas en el futuro, tendría sentido desechar la caché para los elementos que al desplazarse salen del área visible de la lista virtualizada. Esto debería estar a tono con tus expectativas si dependes de
useMemo
únicamente como una optimización de rendimiento. De lo contrario, una variable de estado o una ref pueden ser más apropiadas.
Uso
Evitar recálculos costosos
Para almacenar en caché un cálculo entre renderizados, envuélvelo en useMemo
en el nivel superior de tu componente:
import { useMemo } from 'react';
function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
}
Necesitas pasar dos cosas a useMemo
:
- Una función de cálculo que no toma argumentos, como
() =>
, y devuelve lo que querías calcular. - Una lista de dependencias que incluye cada valor dentro de tu componente que se usa dentro del cálculo.
En el renderizado inicial, elvalor que obtendrás de useMemo
será el resultado de llamar a tu cálculo.
En cada procesamiento posterior, React comparará las dependencias con las dependencias que pasaste durante el último renderizado. Si ninguna de las dependencias ha cambiado (comparado con Object.is
), useMemo
devolverá el valor que ya calculó antes. De lo contrario, React volverá a ejecutar tu cálculo y devolverá el nuevo valor.
En otras palabras, useMemo
guarda en caché un cálculo entre renderizados hasta que cambian sus dependencias.
Veamos un ejemplo para ver cuándo es útil.
De forma predeterminada, React volverá a ejecutar todo el cuerpo de tu componente cada vez que se vuelva a renderizar. Por ejemplo, si esta TodoList
actualiza su estado o recibe nuevas props de su padre, la función filterTodos
se volverá a ejecutar:
function TodoList({ todos, tab, theme }) {
const visibleTodos = filterTodos(todos, tab);
// ...
}
Por lo general, esto no es un problema porque la mayoría de los cálculos son muy rápidos. Sin embargo, si estás filtrando o transformando un array grande, o estás realizando algún cálculo costoso, es posible que desees omitir hacerlo nuevamente si los datos no han cambiado. Si tanto todos
como tab
son los mismos que durante el último renderizado, envolver el cálculo en useMemo
como antes te permite reutilizar visibleTodos
que ya se calculó antes.
Este tipo de almacenamiento en caché se denomina memoización.
Profundizar
En general, a menos que estés creando o recorriendo miles de objetos, probablemente no sea costoso. Si deseas tener más confianza, puede agregar un registro de consola para medir el tiempo dedicado a una pieza de código:
console.time('filter array');
const visibleTodos = filterTodos(todos, tab);
console.timeEnd('filter array');
Realiza la interacción que estás midiendo (por ejemplo, escribiendo en la entrada de texto). Luego verás registros como filter array: 0.15ms
en tu consola. Si el tiempo total registrado suma una cantidad significativa (por ejemplo, ‘1 ms’ o más), podría tener sentido memoizar ese cálculo. Como experimento, puedes envolver el cálculo en useMemo
para verificar si el tiempo total registrado ha disminuido para esa interacción o no:
console.time('filter array');
const visibleTodos = useMemo(() => {
return filterTodos(todos, tab); // Se omite si `todos` y `tab` no han cambiado.
}, [todos, tab]);
console.timeEnd('filter array');
useMemo
no hará que el primer renderizado sea más rápido. Solo te ayuda a omitir el trabajo innecesario en las actualizaciones.
Ten en cuenta que tu máquina probablemente sea más rápida que la de tus usuarios, por lo que es una buena idea probar el rendimiento con una ralentización artificial. Por ejemplo, Chrome ofrece para esto una opción de CPU Throttling.
También ten en cuenta que medir el rendimiento en desarrollo no te dará los resultados más precisos. (Por ejemplo, cuando el Modo estricto está activado, verás que cada componente se renderiza dos veces en lugar de una vez). Para obtener los tiempos más precisos, construye tu aplicación para producción y pruébala en un dispositivo como el que tienen tus usuarios.
Profundizar
Si tu aplicación es como este sitio y la mayoría de las interacciones son bruscas (como reemplazar una página o una sección completa), la memoización generalmente no es necesaria. Por otro lado, si tu aplicación se parece más a un editor de dibujos y la mayoría de las interacciones son granulares (como formas en movimiento), entonces la memoización podría resultarte muy útil.
Optimizar con useMemo
solo es valioso en algunos casos:
- El cálculo que estás poniendo en
useMemo
es notablemente lento y tus dependencias rara vez cambian. - Lo pasas como prop a un componente envuelto en
memo
. Quieres omitir el rerenderizado si el valor no ha cambiado. La memoización permite que tu componente se vuelva a renderizar solo cuando las dependencias no son las mismas. - El valor que estás pasando se usa más tarde como una dependencia de algún Hook. Por ejemplo, tal vez otro cálculo con
useMemo
depende de este. O tal vez dependas de este valor dentro de unuseEffect.
No hay ningún beneficio en envolver un cálculo en useMemo
en otros casos. Tampoco hay un daño significativo en hacerlo, por lo que algunos equipos optan por no pensar en casos individuales y memoizar tanto como sea posible. La desventaja de este enfoque es que el código se vuelve menos legible. Además, no toda la memoización es efectiva: un solo valor que es «siempre nuevo» es suficiente para interrumpir la memoización de un componente completo.
En la práctica, puedes hacer que muchas memoizaciones sean innecesarias siguiendo algunos principios:
- Cuando un componente envuelve visualmente otros componentes, déjalo aceptar JSX como hijos. De esta manera, cuando el componente contenedor actualice su propio estado, React sabe que sus hijos no necesitan volverse a renderizar.
- Prefiere el estado local y no eleves el estado más allá de lo necesario. Por ejemplo, no mantengas el estado transitorio —como formularios y si se le está haciendo hover a un elemento— en la parte superior de tu árbol o en una biblioteca de estado global.
- Mantén tu lógica de renderizado pura. Si volver a renderizar un componente causa un problema o produce algún artefacto visual notable, ¡es un error en tu componente! Soluciona el error en lugar de agregar memoización.
- Evita Efectos innecesarios que actualicen el estado. La mayoría de los problemas de rendimiento en las aplicaciones de React son causados por cadenas de actualizaciones que se originan en Efectos que hacen que tus componentes se rendericen una y otra vez.
- Intenta eliminar las dependencias innecesarias de tus Efectos. Por ejemplo, en lugar de memoizar, suele ser más sencillo mover algún objeto o función dentro de un Efecto o fuera del componente.
Si una interacción específica aún se siente lenta, usa el generador de perfiles de la Herramientas de Desarrollo de React para ver qué componentes se beneficiarían más de la memoización y agregar memoización donde sea necesario. Estos principios hacen que tus componentes sean más fáciles de depurar y comprender, por lo que es bueno seguirlos en cualquier caso. A largo plazo, estamos investigando hacer memoización granular automáticamente para solucionar esto de una vez por todas.
Ejemplo 1 de 2: Saltarse el recálculo con useMemo
En este ejemplo, la implementación de filterTodos
se ralentiza artificialmente para que puedas ver qué sucede cuando alguna función de JavaScript que estás llamando durante el renderizado es realmente lenta. Intenta cambiar las pestañas y alternar el tema.
Cambiar las pestañas se siente lento porque obliga a que el filterTodos
ralentizado se vuelva a ejecutar. Eso es de esperar porque tab
ha cambiado, por lo que todo el cálculo necesita volver a ejecutarse. (Si tienes curiosidad por qué se ejecuta dos veces, se explica aquí.)
A continuación, intenta alternar el tema. ¡Gracias a useMemo
, es rápido a pesar de la ralentización artificial! La llamada lenta a filterTodos
se omitió porque tanto todos
como tab
(que pasas como dependencias a useMemo
) no han cambiado desde el último renderizado.
import { useMemo } from 'react'; import { filterTodos } from './utils.js' export default function TodoList({ todos, theme, tab }) { const visibleTodos = useMemo( () => filterTodos(todos, tab), [todos, tab] ); return ( <div className={theme}> <p><b>Nota: ¡<code>filterTodos</code> se ralentiza artificialmente!</b></p> <ul> {visibleTodos.map(todo => ( <li key={todo.id}> {todo.completed ? <s>{todo.text}</s> : todo.text } </li> ))} </ul> </div> ); }
Omitir el rerenderizado de componentes
En algunos casos, useMemo
también puede ayudar a optimizar el rendimiento del rerenderizado de componentes hijos. Para ilustrar esto, digamos que este componente TodoList
pasa visibleTodos
como prop al componente hijo List
:
export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}
Te habrás dado cuenta de que alternar la prop theme
congela la aplicación por un momento, pero si eliminas <List />
de tu JSX, se siente rápido. Esto te dice que vale la pena intentar optimizar el componente List
.
De forma predeterminada, cuando un componente se vuelve a renderizar, React vuelve a renderizar a todos sus hijos de forma recursiva. Por eso, cuando TodoList
se vuelve a renderizar con un theme
diferente, el componente List
también se vuelve a renderizar. Esto está bien para componentes que no requieren mucho cálculo para volverse a renderizar. Pero si has verificado que un nuevo renderizado es lento, puedes decirle a List
que omita el nuevo renderizado cuando sus props sean las mismos que en el último renderizado envolviéndola en memo
:
import { memo } from 'react';
const List = memo(function List({ items }) {
// ...
});
Con este cambio, List
omitirá volver a renderizar si todas sus props son las mismas que en el último renderizado. ¡Aquí es donde guardar en caché el cálculo se vuelve importante! Imagina que calculaste visibleTodos
sin useMemo
:
export default function TodoList({ todos, tab, theme }) {
// Cada vez que cambie el tema, este será una *array* diferente...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... por lo que las props de List nunca serán las mismas, y se volverán a renderizar cada vez */}
<List items={visibleTodos} />
</div>
);
}
En el ejemplo anterior, la función filterTodos
siempre crea una array diferente, similar a cómo el objeto literal {}
siempre crea un nuevo objeto. Normalmente, esto no sería un problema, pero significa que las props de List
nunca serán las mismas, y tu optimización con memo
no funcionará. Aquí es donde useMemo
es útil:
export default function TodoList({ todos, tab, theme }) {
// Le dice a React que guarde en caché tu cálculo entre renderizados...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...así que mientras estas dependencias no cambien...
);
return (
<div className={theme}>
{/* ...La lista recibirá las mismos props y puedes omitir el rerenderizado */}
<List items={visibleTodos} />
</div>
);
}
Al envolver el cálculo de visibleTodos
en useMemo
, te aseguras de que tenga el mismo valor entre los renderizados (hasta que cambien las dependencias). No tienes que envolver un cálculo en useMemo
a menos que lo hagas por alguna razón específica. En este ejemplo, la razón es que lo pasas a un componente envuelto en memo
, y esto te permite omitir el rerenderizado. Hay algunas otras razones para agregar useMemo
que se describen más adelante en esta página.
Profundizar
En lugar de envolver List
en memo
, podrías envolver el nodo de JSX <List />
en useMemo
:
export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
return (
<div className={theme}>
{children}
</div>
);
}
El comportamiento sería el mismo. Si visibleTodos
no ha cambiado, List
no se rerenderizará.
Un nodo de JSX como <List items={visibleTodos} />
es un objeto como { type: List, props: { items: visibleTodos } }
. Crear este objeto es muy barato, pero React no sabe si su contenido es el mismo que la última vez o no. Esta es la razón por la que, de forma predeterminada, React volverá a renderizar el componente List
.
Sin embargo, si React ve exactamente el mismo JSX que durante el renderizado anterior, no intentará volver a renderizar tu componente. Esto se debe a que los nodos JSX son inmutables. Un objeto de nodo JSX no podría haber cambiado con el tiempo, por lo que React sabe que es seguro omitir un nuevo renderizado. Sin embargo, para que esto funcione, el nodo tiene que ser realmente el mismo objeto, no simplemente tener el mismo aspecto en el código. Esto es lo que hace useMemo
en este ejemplo.
Envolver manualmente los nodos JSX en useMemo
no es conveniente. Por ejemplo, no puedes hacerlo condicionalmente. Comúnmente es por esto que se envuelven los componentes con memo
en lugar de envolver los nodos de JSX.
Ejemplo 1 de 2: Omitir el rerenderizado con useMemo
y memo
En este ejemplo, el componente List
se ralentiza artificialmente para que puedas ver qué sucede cuando un componente React que estás renderizando es realmente lento. Intenta cambiar las pestañas y alternar el tema.
Cambiar las pestañas se siente lento porque obliga a que List
se rerenderice. Eso es de esperar porque tab
ha cambiado, y necesitas reflejar la nueva elección del usuario en la pantalla.
A continuación, intenta alternar el tema. ¡Gracias a useMemo
junto con memo
, es rápido a pesar de la ralentización artificial! El componente List
omitió rerenderizar porque el array visibleTodos
no ha cambiado desde el último renderizado. El array visibleTodos
no ha cambiado porque tanto todos
como tab
(que pasas como dependencias a useMemo
) no han cambiado desde el último renderizado.
import { useMemo } from 'react'; import List from './List.js'; import { filterTodos } from './utils.js' export default function TodoList({ todos, theme, tab }) { const visibleTodos = useMemo( () => filterTodos(todos, tab), [todos, tab] ); return ( <div className={theme}> <p><b>Nota: ¡<code>List</code> se ralentiza artificialmente!</b></p> <List items={visibleTodos} /> </div> ); }
Preventing an Effect from firing too often
Sometimes, you might want to use a value inside an Effect:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const options = {
serverUrl: 'https://localhost:1234',
roomId: roomId
}
useEffect(() => {
const connection = createConnection(options);
connection.connect();
// ...
This creates a problem. Every reactive value must be declared as a dependency of your Effect. However, if you declare options
as a dependency, it will cause your Effect to constantly reconnect to the chat room:
useEffect(() => {
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // 🔴 Problem: This dependency changes on every render
// ...
To solve this, you can wrap the object you need to call from an Effect in useMemo
:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const options = useMemo(() => {
return {
serverUrl: 'https://localhost:1234',
roomId: roomId
};
}, [roomId]); // ✅ Only changes when roomId changes
useEffect(() => {
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [options]); // ✅ Only changes when createOptions changes
// ...
This ensures that the options
object is the same between re-renders if useMemo
returns the cached object.
However, since useMemo
is performance optimization, not a semantic guarantee, React may throw away the cached value if there is a specific reason to do that. This will also cause the effect to re-fire, so it’s even better to remove the need for a function dependency by moving your object inside the Effect:
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
const options = { // ✅ No need for useMemo or object dependencies!
serverUrl: 'https://localhost:1234',
roomId: roomId
}
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ Only changes when roomId changes
// ...
Now your code is simpler and doesn’t need useMemo
. Learn more about removing Effect dependencies.
Memoizar una dependencia de otro Hook
Supón que tienes un cálculo que depende de un objeto creado directamente en el cuerpo del componente:
function Dropdown({ allItems, text }) {
const searchOptions = { matchMode: 'whole-word', text };
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 Precaución: Dependencia de un objeto creado en el cuerpo del componente
// ...
Depender de un objeto como este anula el sentido de la memoización. Cuando un componente se vuelve a renderizar, todo el código directamente dentro del cuerpo del componente se vuelve a ejecutar. Las líneas de código que crean el objeto searchOptions
también se ejecutarán en cada renderizado. Dado que searchOptions
es una dependencia de tu llamada a useMemo
, y es diferente cada vez, React sabrá que las dependencias son diferentes desde la última vez, y recalculará searchItems
cada vez.
Para solucionar esto, puedes memoizar al propio objeto searchOptions
antes de pasarlo como una dependencia:
function Dropdown({ allItems, text }) {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ Solo cambia cuando cambia el texto
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ Solo cambia cuando cambia allItems o searchOptions
// ...
En el ejemplo anterior, si text
no cambia, el objeto searchOptions
tampoco cambiará. Sin embargo, una solución aún mejor es mover la declaración del objeto searchOptions
dentro de la función de cálculo de useMemo
:
function Dropdown({ allItems, text }) {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ✅ Solo cambia cuando cambia `allItems` o text
// ...
Ahora tu cálculo depende directamente de text
(que es un string y no puede ser «accidentalmente» nuevo como un objeto).
Puedes usar un enfoque similar para evitar que useEffect
vuelva a activarse innecesariamente. Antes de intentar optimizar las dependencias con useMemo
, ve si puede hacerlas innecesarias. Lee sobre la eliminación de dependencias de Efectos.
Memoizar una función
Supongamos que el componente Form
está envuelto en memo
. Deseas pasarle una función como prop:
export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}
return <Form onSubmit={handleSubmit} />;
}
De forma similar a cómo {}
siempre crea un objeto diferente, declaraciones de funciones como function() {}
y expresiones como () => {}
producen una función diferente en cada renderizado. Por sí mismo, crear una nueva función no es un problema. ¡Esto no es algo que haya que evitar! Sin embargo, si el componente Form
está memoizado, presumiblemente querrás omitir volver a renderizarlo cuando no haya cambiado ninguna prop. Una prop que es siempre diferente anularía el sentido de la memoización.
Para memoizar una función con useMemo
, su función de cálculo tendría que devolver otra función:
export default function Page({ productId, referrer }) {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
¡Esto parece torpe! Memoizar funciones es lo suficientemente común como para que React tenga un Hook incorporado específicamente para eso. Envuelve tus funciones en useCallback
en lugar de useMemo
para evitar tener que escribir una función anidada adicional:
export default function Page({ productId, referrer }) {
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
Los dos ejemplos anteriores son completamente equivalentes. El único beneficio de useCallback
es que te permite evitar escribir una función anidada adicional dentro. No hace nada más. Lee más sobre useCallback
.
Solución de problemas
Mi cálculo se ejecuta dos veces en cada renderizado
En Modo estricto, React llamará a algunas de tus funciones dos veces en lugar de una:
function TodoList({ todos, tab }) {
// Esta función de componente se ejecutará dos veces por cada renderizado.
const visibleTodos = useMemo(() => {
// Este cálculo se ejecutará dos veces si alguna de las dependencias cambia.
return filterTodos(todos, tab);
}, [todos, tab]);
// ...
Esto se espera y no debería romper tu código.
Este comportamiento aplica solo en desarrollo y te ayuda a mantener los componentes puros. React usa el resultado de una de las llamadas e ignora el resultado de la otra llamada. Siempre que tus funciones de componente y cálculo sean puras, no debería afectar tu lógica. Sin embargo, si accidentalmente son impuras, esto te ayuda a detectar los errores y corregirlos.
Por ejemplo, esta función de cálculo impuro muta un array que recibió como prop:
const visibleTodos = useMemo(() => {
// 🚩 Error: mutar la prop
todos.push({ id: 'last', text: 'Go for a walk!' });
const filtered = filterTodos(todos, tab);
return filtered;
}, [todos, tab]);
Debido a que React llama a tu cálculo dos veces, verás que la tarea pendiente se agregó dos veces, por lo que sabrás que hay un error. Tu cálculo no puede cambiar los objetos que recibió, pero puede cambiar cualquier objeto nuevo que haya creado durante el cálculo. Por ejemplo, si filterTodos
siempre devuelve un array diferente, puedes mutar ese array:
const visibleTodos = useMemo(() => {
const filtered = filterTodos(todos, tab);
// ✅ Correcto: mutar un objeto que creaste durante el cálculo
filtered.push({ id: 'last', text: 'Go for a walk!' });
return filtered;
}, [todos, tab]);
Lee mantener los componentes puros para obtener más información sobre la pureza.
Además, consulte las guías sobre actualización de objetos y actualización de arrays sin mutación.
Se supone que mi llamada a useMemo
devuelve un objeto, pero devuelve undefined
Este código no funciona:
// 🔴 No puedes devolver un objeto desde una función flecha con () => {
const searchOptions = useMemo(() => {
matchMode: 'whole-word',
text: text
}, [text]);
En JavaScript, () => {
inicia el cuerpo de la función flecha, por lo que la llave {
no es parte de tu objeto. Es por eso que no devuelve un objeto y conduce a errores confusos. Podrías arreglarlo agregando paréntesis como ({
y })
:
// Esto funciona, pero es fácil que alguien lo rompa de nuevo.
const searchOptions = useMemo(() => ({
matchMode: 'whole-word',
text: text
}), [text]);
Sin embargo, esto sigue siendo confuso y demasiado fácil de romper eliminando los paréntesis.
Para evitar este error, escriba una declaración return
explícitamente:
// ✅ Esto funciona y es explícito.
const searchOptions = useMemo(() => {
return {
matchMode: 'whole-word',
text: text
};
}, [text]);
Cada vez que mi componente se renderiza, el cálculo en useMemo
vuelve a ejecutarse
¡Asegúrate de haber especificado el array de dependencias como segundo argumento!
Si olvidas el array de dependencia, useMemo
volverá a ejecutar el cálculo cada vez:
function TodoList({ todos, tab }) {
// 🔴 Recalcula cada vez: sin *array* de dependencias
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ...
Esta es la versión corregida que pasa el array de dependencia como segundo argumento:
function TodoList({ todos, tab }) {
// ✅ No recalcula innecesariamente
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
Si esto no ayuda, entonces el problema es que al menos una de tus dependencias es diferente del renderizado anterior. Puedes depurar este problema registrando manualmente tus dependencias en la consola:
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);
Luego, puedes hacer clic derecho en los arrays de diferentes renderizados en la consola y seleccionar «Almacenar como una variable global» para ambas. Suponiendo que el primero se guardó como temp1
y el segundo se guardó como temp2
, puedes usar la consola del navegador para verificar si cada dependencia en ambos arrays es la misma:
Object.is(temp1[0], temp2[0]); // ¿La primera dependencia es la misma entre los *arrays*?
Object.is(temp1[1], temp2[1]); // ¿La segunda dependencia es la misma entre los *arrays*?
Object.is(temp1[2], temp2[2]); // ... y así sucesivamente para cada dependencia ...
Cuando encuentres qué dependencia está interrumpiendo la memoización, busca una manera de eliminarla o memoízala también.
Necesito llamar a useMemo
para cada elemento de la lista en un bucle, pero no está permitido
Supongamos que el componente Chart
está envuelto en memo
. Quieres omitir volver a renderizar cada Chart
en la lista cuando el componente ReportList
se vuelve a renderizar. Sin embargo, no puedes llamar a useMemo
en un bucle:
function ReportList({ items }) {
return (
<article>
{items.map(item => {
// 🔴 No puedes llamar a useMemo en un bucle como este:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure key={item.id}>
<Chart data={data} />
</figure>
);
})}
</article>
);
}
En su lugar, extrae un componente para cada elemento y memoiza los datos de elementos individuales:
function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}
function Report({ item }) {
// ✅ Llama a useMemo en el nivel superior:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure>
<Chart data={data} />
</figure>
);
}
Alternativamente, puedes eliminar useMemo
y en su lugar envolver Report
en memo
. Si la prop item
no cambia, Report
omitirá el rerenderizado, y por tanto Chart
también lo hará:
function ReportList({ items }) {
// ...
}
const Report = memo(function Report({ item }) {
const data = calculateReport(item);
return (
<figure>
<Chart data={data} />
</figure>
);
});