El hook useCallback y React.memo

Descubre lo bien que trabajan juntos este hooks y la utilidad React.memo para memorizar componentes

Hace unos meses escribí un artículo en donde hablaba de los hooks y que nos permiten optimizar el rendimiento de nuestra aplicación:

Hoy quiero escribir un artículo complementario en donde veamos una de las aplicaciones más interesantes que tiene el hook , ya que creo que de este modo resulta mucho más fácil encontrarle utilidad y animarnos a usarlo más a menudo.

¡Comencemos!

El hook useCallback

Antes de comenzar recordemos cómo funciona el hook . Este “hook” nos permite memorizar la función que le pasemos como argumento, devolviéndonos siempre la misma “instancia” render tras render hasta que cambie alguna de las dependencias que especifiquemos.

Las dependencias del “hook” funcionan del mismo modo que las del hook : son especificadas por medio de un array que pasamos como segundo argumento al hook. En el momento en que alguna de ellas cambie recibiremos una nueva versión de la función.

import {useCallback} from 'react';function Component() {

// ....
const onClick = useCallback(() => console.log('Click'); []);
return (<div>
<MyButton onClick={onClick}>Púlsame</MyButton>
...
);
}

Su uso suele estar muy asociado a la creación de “custom hooks” ya que es una buena práctica devolver memorizadas las funciones que en ellos creemos para que puedan ser empleadas dentro de otros hooks (como el hook ) sin miedo a que causen problemas al emplearlas como dependencias.

import {useCallback, useState} from 'react';

export default function useCount(initialValue = 0) {
const [count, setCount] = useState(initialValue);

const increment = useCallback(function() {
setCount(count => count + 1);
}, [setCount]);


return [count, increment];
}

Gracias a este uso del hook podemos emplear la función dentro de un efecto sin miedo a declararlo en las dependencias, ya que siempre obtendremos la misma función:

useEffect(function() {
// something
console.log(otherDep);
increment();
}, [increment, otherDep]);

Sin embargo, tiene otro uso muy interesante como estamos a punto de ver.

React.memo

La función nos permite memorizar un componente. Esto permite a React evitar renderizarlo si sus propiedades no han cambiado, con la consiguiente optimización en lo que a rendimiento se refiere.

import React from 'react';function Footer({user}) {
return (...);
}
export default React.memo(Footer);

En este caso, cuando empleemos el componente en nuestra aplicación, React evitará renderizarlo de nuevo siempre y cuando su propiedad no cambie.

🤓 Quizás ya adivines lo que estoy a punto de contarte.

Combinando y React.memo

Para entender lo que estoy a punto de contarte es importante que recuerdes que las funciones que declaramos en nuestros componentes se crean cada vez que el componente se renderiza.

Esto quiere decir lo siguiente:

// 1️⃣ MyButton.jsfunction MyButton({onClick}) {
return <button onClick={onClick}>Pulsar</button>
}
export default React.memo(MyButton);// 2️⃣ Component.jsfunction Component() {

// ....
function onClick() {
...
}
return (<div>
<MyButton onClick={onClick}>Púlsame</MyButton>
...
);
}

Cada vez que el componente se renderiza estamos creando una nueva función y por tanto, está recibiendo en cada render una nueva función .

Esto significa que memorizar el componente por medio de no tendría efecto ya que la propiedad del render “N” sería distinta de la del render “N+1”.

De una forma simplificada puedes pensarlo de este modo:

function createFn() {
return function foo() { console.log('Hola'); }
}
const fn1 = createFn();
const fn2 = createFn();
console.log(fn1 === fn2); // false

Sin embargo, gracias al hook podemos memorizar las funciones para obtener en cada render la misma “instancia” siempre y cuando las dependencias no cambien.

// 1️⃣ MyButton.jsfunction MyButton({onClick}) {
return <button onClick={onClick}>Pulsar</button>
}
export default React.memo(MyButton);// 2️⃣ Component.jsfunction Component() {

// ....
const onClick = useCallback(() => console.log('Click'); []);
return (<div>
<MyButton onClick={onClick}>Púlsame</MyButton>
...
);
}

Como en este caso el array de dependencias del hook se encuentra vacío, render tras render obtendremos la misma función , lo cual permitirá a React aprovechar la memorización del componente y renderizarlo una única vez.

Ejemplo

Como al principio estas cosas son difíciles de ver he creado el siguiente CodeSandbox.

En él estoy definiendo un hook que devuelve una función y su correspondiente versión memorizada.

El componente del proyecto pinta un botón que al pulsar invoca a la función que recibe como propiedad. Este componente se encuentra memorizado gracias a .

El componente lo uso dos veces:

  • Una pasándole la versión memorizada de la función .
  • Otra pasándole la versión sin memorizar.

Al pulsar los botones podrás ver como el que emplea la versión memorizada de no vuelve a renderizarse, algo que sí sucede con el componente que usa la versión sin memorizar.

Conclusiones

Como has podido ver, el hook nos mete de lleno en un terreno muy interesante como es la optimización de los renders que React realiza, algo que suele ser de vital importancia en aplicaciones de gran tamaño.

Emplearlo en conjunción con la utilidad React.memo nos puede ahorrar numerosos renders de componentes pesados (por ejemplo la cabecera y el footer), algo que terminaremos agradeciendo más pronto que tarde.

¿Quieres recibir más artículos como este?

Si te ha gustado este artículo te animo a que te suscribas a la newsletter que envío cada domingo con publicaciones similares a esta y más contenido recomendado: 👇👇👇

Written by

Entre paseo y paseo con Simba desarrollo en Symfony y React

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store