
Los usuarios disfrutan de interfaces de usuario (UI) rápidas y responsivas. Un retraso en la respuesta de la interfaz de usuario de menos de 100 milisegundos se siente instantáneo para el usuario, pero un retraso entre 100 y 300 milisegundos ya es perceptible.
Para mejorar el rendimiento de la interfaz de usuario, React ofrece un componente de orden superior (HOC) llamado React.memo(). Cuando un componente hace uso de React.memo(), React memoriza la salida procesada del componente y luego omite los renderizados innecesarios.
Este articulo describe las situaciones en las que React.memo() mejora el rendimiento y, en que situaciones no debemos utilizarlo. Además, describiré algunos consejos útiles de memorización que debe tener en cuenta.
React.memo()
Al decidir actualizar el DOM, React primero renderiza el componente y luego compara el resultado con el renderizado anterior. Si los resultados del renderizado son diferentes, React actualiza el DOM. La comparación de resultados de renderizado actual vs anterior es rápida. Pero puede acelerar el proceso en algunas circunstancias.
Cuando un componente utiliza React.memo(), React renderiza el componente y memoriza el resultado. Antes del siguiente renderizado, si las nuevas propiedades son las mismas, React reutiliza el resultado memorizado omitiendo el siguiente renderizado.
Veamos la memorización en acción. El componente funcional Movie está haciendo uso de React.memo():
export function Movie({ title, releaseDate }) {
return (
<div>
<div>Movie title: {title}</div>
<div>Release date: {releaseDate}</div>
</div>
);
}
export const MemoizedMovie = React.memo(Movie);
React.memo(Movie) devuelve un nuevo componente memorizado MemoizedMovie. MemoizedMovie genera el mismo contenido que el componente original, pero con una diferencia: el renderizado de MemoizedMovie se memoriza. React reutiliza el contenido memorizado siempre que las propiedades title y releaseDate sean los mismos entre renderizados:
// Primer renderizado - MemoizedMovie es llamado.
<MemoizedMovie
title="Heat"
releaseDate="December 15, 1995"
/>
// Segundo renderizado - MemoizedMovie no es llamado.
<MemoizedMovie
title="Heat"
releaseDate="December 15, 1995"
/>
Comparar manualmente las propiedades del componente
Por defecto, React.memo() hace una comparación shallow de las propiedades y objetos de las propiedades. Para personalizar la comparación de las propiedades, puede usar el segundo argumento para indicar una función de verificación de igualdad:
React.memo(Component, [areEqual(prevProps, nextProps)]);
La función areEqual(prevProps, nextProps) debe devolver verdadero si prevProps y nextProps son iguales. Por ejemplo, calculemos manualmente si las propiedades del componente Movie son iguales:
function moviePropsAreEqual(prevMovie, nextMovie) {
return prevMovie.title === nextMovie.title
&& prevMovie.releaseDate === nextMovie.releaseDate;
}
const MemoizedMovie2 = React.memo(Movie, moviePropsAreEqual);
La función moviePropsAreEqual() devuelve verdadero si las propiedades anteriores y siguientes son iguales.
Cuándo usar React.memo()
- Componente funcional puro: El componente es funcional, y dado las mismas propiedades siempre retorna el mismo resultado.
- Se renderiza a menudo: El componente se renderiza muchas veces.
- Se vuelve a renderizar con las mismas propiedades: El componente se vuelve a renderizar cada vez que recibe las mismas propiedades.
- Componente de tamaño mediano o grande: El componente contiene una gran cantidad de elementos, por lo que es importante hacer una comparativa de las propiedades y evitar renderizados innecesarios.
Los componentes se renderizan a menudo con las mismas propiedades.
El mejor caso para hacer uso de React.memo() es cuando espera que el componente funcional se renderice a menudo con las mismas propiedades. Una situación común que hace que un componente se renderice con las mismas propiedades es que un componente padre lo oblige a renderizar.
Reutilicemos el componente Movie definido anteriormente. Un nuevo componente padre, MovieViewsRealtime, muestra la cantidad de vistas de una película, con actualizaciones en tiempo real:
function MovieViewsRealtime({ title, releaseDate, views }) {
return (
<div>
<Movie title={title} releaseDate={releaseDate} />
Movie views: {views}
</div>
);
}
La aplicación se trae la cantidad de vistas periódicamente desde el servidor en segundo plano (cada segundo), actualizando la propiedad de vistas del componente MovieViewsRealtime.
// primer render
<MovieViewsRealtime
views={0}
title="Forrest Gump"
releaseDate="June 23, 1994"
/>
// después de 1 segundo las vistas son 10
<MovieViewsRealtime
views={10}
title="Forrest Gump"
releaseDate="June 23, 1994"
/>
// después de 2 segundos las vistas son 25
<MovieViewsRealtime
views={25}
title="Forrest Gump"
releaseDate="June 23, 1994"
/>
// etc
Cada vez que se actualiza la propiedad de vistas con un nuevo número, se renderiza MovieViewsRealtime. Esto también hace que el componente Movie se renderice, incluso si el título y la fecha de lanzamiento siguen siendo los mismos. Ese es el caso correcto para aplicar memoización en el componente Movie.
Usemos el componente memorizado MemoizedMovie dentro de MovieViewsRealtime para evitar renderizaciones inútiles:
function MovieViewsRealtime({ title, releaseDate, views }) {
return (
<div>
<MemoizedMovie title={title} releaseDate={releaseDate} />
Movie views: {views}
</div>
)
}
Siempre que las propiedades title y releaseDate sean las mismos, React omite renderizar MemoizedMovie. Esto mejora el rendimiento del componente MovieViewsRealtime.
Cuándo evitar React.memo()
Si el componente no es pesado y generalmente se renderiza con diferentes propiedades, lo más probable es que no necesite React.memo(). Utilice la siguiente regla general: no utilice la memorización si no puede cuantificar las ganancias de rendimiento. El uso inapropiado de React.memo() puede incluso afectar el rendimiento.
Si bien es posible, no es recomendable utilizar React.memo() en componentes de clase. En este caso es mejor heredar de la clase PureComponent o defina una implementación personalizada del método shouldComponentUpdate() si necesita memorización para componentes de clase.
Comparación de propiedades innecesarias
Imagine un componente que generalmente se renderiza con diferentes propiedades. En este caso, la memorización no proporciona beneficios. Incluso si utiliza memorización en este tipo de componente, va a afectar el rendimiento ya que React tendrá que hacer mas trabajo para renderizar el componente:
- Invoca la función de comparación para determinar si las propiedades anteriores y siguientes son iguales
- Debido a que la comparación de las propiedades casi siempre devuelve falso, React realiza la diferencia de los resultados de renderización anteriores y actuales.
No obtiene beneficios de rendimiento, al contrario esta ejecutando la función de comparación en vano.
React.memo() y funciones callback.
El objeto de una función es igual solo a sí mismo. Veamos eso comparando algunas funciones:
function sumFactory() {
return (a, b) => a + b;
}
const sum1 = sumFactory();
const sum2 = sumFactory();
console.log(sum1 === sum2); // => false
console.log(sum1 === sum1); // => true
console.log(sum2 === sum2); // => true
sumFactory() es una factory function. Devuelve funciones que suman 2 números. Las funciones sum1 y sum2 son creadas por esta función fabrica. Ambas funciones suman números. Sin embargo, sum1 y sum2 son objetos de función diferentes (sum1 === sum2 es falso). Cada vez que un componente padre define un callback para su hijo, crea nuevas instancias de funciones. Veamos cómo esto rompe la memorización y cómo solucionarlo.
El siguiente componente Logout acepta el callback onLogout como propiedad:
function Logout({ username, onLogout }) {
return (
<div onClick={onLogout}>
Logout {username}
</div>
);
}
const MemoizedLogout = React.memo(Logout);
Un componente que acepta un callback debe manejarse con cuidado al aplicar la memorización. El componente padre podría proporcionar diferentes instancias de la función callback en cada renderizado:
function MyApp({ store, cookies }) {
return (
<div className="main">
<header>
<MemoizedLogout
username={store.username}
onLogout={() => cookies.clear('session')}
/>
</header>
{store.content}
</div>
);
}
Incluso si se proporciona con el mismo valor de username, MemoizedLogout se renderiza cada vez porque recibe nuevas instancias del callback onLogout
Para solucionarlo, la propiedad onLogout debe recibir la misma instancia del callback. Apliquemos useCallback() para preservar la instancia del callback entre renderizaciones:
const MemoizedLogout = React.memo(Logout);
function MyApp({ store, cookies }) {
const onLogout = useCallback(
() => cookies.clear('session'),
[cookies]
);
return (
<div className="main">
<header>
<MemoizedLogout
username={store.username}
onLogout={onLogout}
/>
</header>
{store.content}
</div>
);
}
useCallback(() => cookies.clear('session'), [cookies]) siempre devuelve la misma instancia de la función siempre que las cookies sean las mismas, por lo que hemos arreglado la memorización de MemoizedLogout.
React.memo() y hooks
Los componentes que usan hooks pueden utilizar libremente React.memo() para lograr la memorización. React siempre vuelve a renderizar el componente si el state cambia, incluso si el componente está haciendo uso de React.memo().
Contenido del articulo
- Comentarios
Comentarios
No hay comentarios. Inicia sesión para comentar.