useMemo
useMemo
es un React Hook que te permite almacenar en caché el resultado de un cálculo entre renderizaciones.
const cachedValue = useMemo(calculateValue, dependencies)
- Uso
- Referencia
- Solución de problemas
- Mi cálculo se ejecuta dos veces en cada renderizado
- Se supone que mi llamada
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
Uso
Evitando recálculos costosos
Para almacenar en caché un cálculo entre renderizaciones, envuélvelo llamando a 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 la cual no toma argumentos, como
() =>
, y devuelve lo que querías calcular. - Una lista de dependencias incluyendo cada valor dentro de su componente que se usa dentro de su cálculo.
En el render inicial, elvalor que obtendrá de useMemo
será el resultado de llamar a su cálculo.
En cada procesamiento posterior, React comparará las dependencias con las dependencias que pasó durante el último procesamiento. Si ninguna de las dependencias ha cambiado (en comparación con Object.is
), useMemo
devolverá el valor que ya calculó antes. De lo contrario, React volverá a ejecutar su cálculo y devolverá el nuevo valor.
En otras palabras, useMemo
almacena en caché un resultado de cálculo entre renderizaciones 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 su componente cada vez que se vuelva a renderizar. Por ejemplo, si esta TodoList
actualiza su estado o recibe nuevos accesorios 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á filtrando o transformando un arreglo grande, o está realizando algún cálculo costoso, es posible que desee omitir hacerlo nuevamente si los datos no han cambiado. Si todos
y tab
son los mismos que durante el último renderizado, envolver el cálculo en useMemo
como antes le permite reutilizar visibleTodos
que ya calculó antes. Este tipo de almacenamiento en caché se denomina memoización.
Deep Dive
¿Cómo saber si un cálculo es costoso?
¿Cómo saber si un cálculo es costoso?
En general, a menos que estés creando o recorriendo miles de objetos, probablemente no sea costoso. Si deseas obtener 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');
Esto mide la interacción que está midiendo (por ejemplo, escribiendo en la entrada). 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 memorizar 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 la pestaña no han cambiado.
}, [todos, tab]);
console.timeEnd('filter array');
useMemo
no hará que el primer renderizado sea más rápido. Solo lo 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 sus usuarios, por lo que es una buena idea probar el rendimiento con una ralentización artificial. Por ejemplo, Chrome ofrece una opción CPU Throttling para esto.
También ten en cuenta que medir el rendimiento en el desarrollo no le dará los resultados más precisos. (Por ejemplo, cuando el Modo estricto está activado, verás que cada componente se procesa dos veces en lugar de una vez). Para obtener los tiempos más precisos, cree su aplicación para producción y pruébela en un dispositivo como sus usuarios.
Deep Dive
¿Debería agregar useMemo en todas partes?
¿Debería agregar useMemo en todas partes?
Si tu aplicación es como este sitio y la mayoría de las interacciones son toscas (como reemplazar una página o una sección completa), la memorización generalmente no es necesaria. Por otro lado, si su 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 memorización le resultará muy útil.
Optimizar con useMemo
solo es valioso en algunos casos:
- El cálculo que estás poniendo en
useMemo
es notablemente lento y sus dependencias rara vez cambian. - Cuando lo que pasa como prop a un componente envuelto en
memo
. Y quieres omitir la re-renderización si el valor no ha cambiado. La memorización permite que tu componente se vuelva a renderizar solo cuando las dependencias 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 valor de cálculo
useMemo
dependa de ello. O tal vez dependa de este valor deuseEffect.
No hay ningún beneficio en envolver un cálculo en useMemo
en otros casos. Tampoco hay un daño significativo en hacer eso, por lo que algunos equipos optan por no pensar en casos individuales y memorizar tanto como sea posible. La desventaja de este enfoque es que el código se vuelve menos legible. Además, no toda la memorización es efectiva: un solo valor que es “siempre nuevo” es suficiente para interrumpir la memorización de un componente completo.
En la práctica, puede hacer que muchas memorizaciones 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 volver a renderizar.
- Preferir el estado local y no elevar el estado más allá de lo necesario. Por ejemplo, no mantenga el estado transitorio como formularios y si un elemento se encuentra en la parte superior de su árbol o en una biblioteca de estado global.
- Mantenga su 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 memorización.
- Evite Efectos innecesarios que actualizan 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 sus componentes se rendericen una y otra vez.
- Intenta eliminar las dependencias innecesarias de sus Efectos. Por ejemplo, en lugar de memorizar, 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 React Developer Tools para ver qué componentes se beneficiarían más de la memorización y agregar memorización donde sea necesario. Estos principios hacen que sus 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 memorizació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 pueda ver qué sucede cuando alguna función de JavaScript que está llamando durante el renderizado es realmente lenta. Intente 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 la tab
ha cambiado, por lo que todo el cálculo necesita volver a ejecutarse. (Si tiene 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 filterTodos
se omitió porque tanto todos
como tab
(que pasa como dependencias a useMemo
) no han cambiado desde entonces el último render.
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 la re-renderización de componentes
En algunos casos, useMemo
también puede ayudar a optimizar el comportamiento de volver a renderizar componentes secundarios. Para ilustrar esto, digamos que este componente TodoList
pasa visibleTodos
como prop al componente secundario 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 le 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 elementos secundarios de forma recursiva. Por eso, cuando TodoList
se vuelve a renderizar con un theme
diferente, el componente List
también vuelve a renderizar. Esto está bien para componentes que no requieren mucho cálculo para volver a renderizar. Pero si has verificado que una nueva renderización es lenta, puedes decirle a List
que omita la nueva renderización cuando sus props sean los mismos que en la última renderización envolviéndola en memo
:
import { memo } from 'react';
const List = memo(function List({ items }) {
// ...
});
Con este cambio, List
omitirá volver a renderizar si todos sus accesorios son mismos que en el último renderizado. ¡Aquí es donde el almacenamiento en caché del cálculo se vuelve importante! Imagina que calculaste visibleTodos
sin useMemo
:
export default function TodoList({ todos, tab, theme }) {
// Cada vez que cambie el tema, esta será una arreglo diferente...
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
{/* ... por lo que los accesorios de List nunca serán los mismos, y se volverán a renderizar cada vez */}
<List items={visibleTodos} />
</div>
);
}
En el ejemplo anterior, la función filterTodos
siempre crea una arreglo 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 su optimización memo
no funcionará. Aquí es donde useMemo
es útil:
export default function TodoList({ todos, tab, theme }) {
// Le dice a React que almacene en caché su cálculo entre renderizaciones...
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab] // ...así que mientras estas dependencias no cambien...
);
return (
<div className={theme}>
{/* ...La lista recibirá los mismos props y puedes omitir la re-renderización */}
<List items={visibleTodos} />
</div>
);
}
Al envolver el cálculo de visibleTodos
en useMemo
, te aseguras de que tenga el mismo valor entre las representaciones (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 pasa a un componente envuelto en memo
, y esto le permite omitir la nueva representación. Hay algunas otras razones para agregar useMemo
que se describen más adelante en esta página.
Deep Dive
Memorización de nodos JSX individuales
Memorización de nodos JSX individuales
En lugar de envolver List
en memo
, podrías envolver el nodo <List />
JSX 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 volverá a representar.
Un nodo 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 representar el componente List
.
Sin embargo, si React ve exactamente el mismo JSX que durante el renderizado anterior, no intentará volver a renderizar su 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 una nueva representación. 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 hacer esto condicionalmente. Por lo general, envolvería los componentes con memo
en lugar de envolver los nodos JSX.
Ejemplo 1 de 2: Omitiendo el volver a renderizar con useMemo
y memo
En este ejemplo, el componente List
se ralentiza artificialmente para que pueda ver qué sucede cuando un componente React que está 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 vuelva a procesar. Eso es de esperar porque la tab
ha cambiado, por lo que debe 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ó volver a renderizar porque el arreglo visibleItems
no ha cambiado desde entonces el último render. El arreglo visibleItems
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> ); }
Memorizando una dependencia de otro Hook
Supon 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 punto de memorizació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 su llamada 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, puede memorizar el objeto searchOptions
en sí mismo 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 el text
no cambió, 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 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 el text
// ...
Ahora su cálculo depende directamente del text
(que es una cadena y no puede ser “accidentalmente” nuevo como un objeto).
Puede usar un enfoque similar para evitar que useEffect
vuelva a activarse innecesariamente. Antes de intentar optimizar las dependencias con useMemo
, vea si puede hacerlas innecesarias. Lea sobre la eliminación de dependencias de efectos.
Memorización de una función
Supongamos que el componente Form
está envuelto en memo
. Desea pasarle una función como accesorio:
export default function ProductPage({ productId, referrer }) {
function handleSubmit(orderDetails) {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
}
return <Form onSubmit={handleSubmit} />;
}
Similar a cómo {}
siempre crea un objeto diferente, declaraciones de funciones como function() {}
y expresiones como () => {}
producen una función diferente en cada renderización. Por sí mismo, crear una nueva función no es un problema. ¡Esto no es algo para evitar! Sin embargo, si el componente Form
está memorizado, presumiblemente querrá omitir volver a renderizarlo cuando no haya cambiado ningúna proop. Una prop que es siempre diferente anularía el punto de memorización.
Para memorizar 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/' + product.id + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
¡Esto parece torpe! Memorizar funciones es lo suficientemente común como para que React tenga un Hook incorporado específicamente para eso. Envuelva sus 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/' + product.id + '/buy', {
referrer,
orderDetails
});
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
}
Los dos ejemplos anteriores son completamente equivalentes. El único beneficio de useCallback
es que le permite evitar escribir una función anidada adicional dentro. No hace nada más. Lea más sobre useCallback
.
Referencia
useMemo(calcularValor, dependencias)
Llama a useMemo
en el nivel superior de tu componente para declarar un valor memorizado:
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 desea memorizar. Debe ser puro, no debe aceptar argumentos y debe devolver un valor de cualquier tipo. React llamará a tu función durante el renderizado inicial. En renderizaciones posteriores, React devolverá el mismo valor nuevamente si lasdependencias
no han cambiado desde la última renderización. 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 su componente. Si su 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
.
Retornos
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
ha devuelto.
Advertencias
useMemo
es un Hook, por lo que solo puede llamarlo en el nivel superior de su componente o sus propios Hooks. No puedes llamarlo dentro de bucles o condiciones. Si lo necesita, extraiga un nuevo componente y mueva el estado a él.- En modo estricto, React llamará a su función de cálculo dos veces para ayudarle a encontrar impurezas accidentales. Esto es solo para desarrollo comportamiento y no afecta la producción. Si su función de cálculo es pura (como debería ser), esto no debería afectar la lógica de su 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á el caché cuando edite el archivo de su componente. Tanto en desarrollo como en producción, React desechará el caché si su componente se suspende durante el montaje inicial. En el futuro, React puede agregar más funciones que se aprovechen de desechar el caché; por ejemplo, si React agrega soporte incorporado para listas virtualizadas en el futuro, tendría sentido desechar el caché para los elementos que se desplazan hacia afuera. de la ventana gráfica de la tabla virtualizada. Esto debería coincidir con sus expectativas si confía en
useMemo
únicamente como una optimización del rendimiento. De lo contrario, una variable de estado o una ref puede ser más apropiado.
Solución de problemas
Mi cálculo se ejecuta dos veces en cada renderizado
En Modo estricto, React llamará a algunas de sus funciones dos veces en lugar de una:
function TodoList({ todos, tab }) {
// Esta función de componente se ejecutará dos veces por cada procesamiento.
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 su código.
Este comportamiento de solo desarrollo lo 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 sus funciones de componente y cálculo sean puras, esto no debería afectar su lógica. Sin embargo, si son impuros accidentalmente, esto le ayuda a detectar los errores y corregirlos.
Por ejemplo, esta función de cálculo impuro muta un arreglo 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 su cálculo dos veces, verá que la tarea pendiente se agregó dos veces, por lo que sabrá que hay un error. Su 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 arreglo diferente, puedes mutar ese arreglo:
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]);
Lea manteniendo los componentes puros para obtener más información sobre la pureza.
Además, consulte las guías sobre actualización de objetos y actualizando arreglos sin mutación.
Se supone que mi llamada useMemo
devuelve un objeto, pero devuelve undefined
Este código no funciona:
// 🔴 No puede devolver un objeto desde una función de flecha con () => {
const searchOptions = useMemo(() => {
matchMode: 'whole-word',
text: text
}, [text]);
En JavaScript, () => {
inicia el cuerpo de la función de flecha, por lo que la llave {
no es parte de su 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 arreglo de dependencias como segundo argumento!
Si olvida el arreglo de dependencia, useMemo
volverá a ejecutar el cálculo cada vez:
function TodoList({ todos, tab }) {
// 🔴 Recalcula cada vez: sin arreglo de dependencia
const visibleTodos = useMemo(() => filterTodos(todos, tab));
// ...
Esta es la versión corregida que pasa el arreglo 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 sus dependencias es diferente del renderizado anterior. Puedes depurar este problema registrando manualmente sus dependencias en la consola:
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
console.log([todos, tab]);
Si esto no ayuda, entonces el problema es que al menos una de tus dependencias es diferente del renderizado anterior. Luego, puede hacer clic con el botón derecho en los arreglos de diferentes renderizaciones 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
, puede usar la consola del navegador para verificar si cada dependencia en ambos arreglos es la misma: puede depurar este problema registrando manualmente sus dependencias en la consola:
Object.is(temp1[0], temp2[0]); // ¿La primera dependencia es la misma entre los arreglos?
Object.is(temp1[1], temp2[1]); // ¿La segunda dependencia es la misma entre los arreglos?
Object.is(temp1[2], temp2[2]); // ... y así sucesivamente para cada dependencia ...
Cuando encuentre qué dependencia está interrumpiendo la memorización, busca una manera de eliminarla o memorícela 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 el 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 memorice los datos de elementos individuales:
function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}
function Report({ item }) {
// ✅ Llame a useMemo en el nivel superior:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure>
<Chart data={data} />
</figure>
);
}
Alternativamente, puede eliminar useMemo
y en su lugar envolver Report
en memo
. Chart
también omitirá la re-renderización:
function ReportList({ items }) {
// ...
}
const Report = memo(function Report({ item }) {
const data = calculateReport(item);
return (
<figure>
<Chart data={data} />
</figure>
);
});