React Hook para optimizar cálculos useMemo

El hook useMemo sirve para memorizar valores lo cual nos ayuda a mejorar el performance de nuestros componentes.

Contenidos:


API de useMemo y complejidad algorítmica

Este hook recibe por parámetro una función para crear un valor a memorizar y por segundo parámetro un array de dependencias.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b])

Es especialmente útil cuando el valor a memorizar es producto de un cálculo que consume "mucha" memoria y procesamiento.

Ya que "mucha" tiende a ser subjetivo, considera la complejidad de un cálculo (o un algoritmo) en base a lo siguiente:

  • Constant: O(1).
  • Logarithmic: O(log n).
  • Linear: O(n).
  • Linearithmic: O(n log n).
  • Quadratic: O(n²).
  • Expontential: O(2^n).
  • Factorial: O(n!).

Un ejemplo clásico de una función que tenga una complejidad cuadrática (O(n²)) es cuando tienes un ciclo dentro de otro ciclo:

const expensiveCalc = (myArray) => {
  for (let i = 0; i < myArray.length; i++) {
    for (let j = 0; j < myArray.length; j++) {
      // code...
    }
  }
}

La pregunta es: ¿Cuántas veces se ejecutará el código dentro del segundo for si pasamos un array con 10,000 elementos?

La respuesta es: (n²) = (10000²) = 1,000, 000, 000 de veces 😱.

Ahora imagina que tienes algo como esto en un componente:

const expensiveCalc = (myArray) => {
  let c = 0

  for (let i = 0; i < myArray.length; i++) {
    for (let j = 0; j < myArray.length; j++) {
      c = c + 1
    }
  }

  console.log('iteraciones:', c)
  return c
}

const MyComponent = ({ someList }) => {
  const result = expensiveCalc(someList)

  return <p>result: {result}</p>
}

Prueba ejecutar lo anterior dando click aquí y observa la consola de la plataforma (no la de tu navegador).

Probablemente no tengas problemas en un inicio dependiendo de lo que necesites lograr en tu componente ya que React por sí mismo es excelente para manejo de performance.

Puede que en un inicio no tengas muchos valores en tu array.

Incluso puede que logres optimizar la complejidad de ese algoritmo usando otra estructura de datos como un diccionario o una tabla hash.

Pero si quieres una solución basado en lo que tiene React, en este caso el hook useMemo viene como anillo al dedo.

¿Ya probaste el código anterior? Si no lo has hecho, te animo a que lo hagas porque ahora vamos a ver la manera de optimizarlo con useMemo.

const MyComponent = ({ someList }) => {
  // Cambio aquí
  const result = useMemo(() => expensiveCalc(someList), [someList])

  return <p>Iteraciones: {result.toLocaleString()}</p>
}

Así de simple. Sólo cambiando una línea de código hemos logrado hacer una optimización de algo que potencialmente puede afectar el performance de nuestra aplicación.

Prueba el código actualizado dando click aquí.

React hook useMemo VS useCallback

Ambos hooks son complementarios para hacer optimizaciones.

Ya hemos visto cómo funciona useMemo y existe un post exclusivo sobre useCallback pero en este apartado quiero dejar claro las diferencias entre ambos.

Mientras useMemo lo usamos para memorizar resultados de cálculos entre renders, useCallback lo usamos para definir una función con referencia equivalente entre renders.

¿Qué es una referencia equivalente? vemos más a fondo sobre esto en el post de useCallback.

PureComponent, React.memo y useMemo

Primero vamos a definir la identidad de cada elemento:

  • PureComponent es una API para declarar componentes de tipo clase.
  • React.memo es un HOC (Componente de orden superior por sus siglas en inglés).
  • useMemo es un hook del core de React.

No podemos hacer ejemplos de equivalencia entre estos elementos porque sería como comparar peras con manzanas.

Lo que podemos hacer es observar cómo funcionan cada uno de ellos.

Para quien no esté familiarizado, PureComponent es parecido a Component que usamos para definir componentes de tipo clase (ambos exportados en el import de React).

PureComponent es usado para componentes que al tener los mismos props y state, renderiza el mismo resultado.

Implementa shouldComponentUpdate internamente, por lo que dados los mismos props y state, el componente va a renderizar el mismo output.

Considera lo siguiente:

class Title extends PureComponent {
  render() {
    return <h1>{this.props.value}</h1>
  }
}

// usar como: <Title value="React Hooks" />

Este es un componente que sólo renderiza un elemento visual, no tiene lógica de llamada a api ni nada por el estilo.

En este caso es conveniente usar PureComponent debido a que nos ahorra hacer la comparación manualmente en si debe o no renderizar.

Su equivalente en Component usando shouldComponentUpdate es:

class Title extends Component {
  shouldComponentUpdate(nextProps) {
    // retorna true si son diferentes
    // si son diferentes, renderiza de nuevo
    return nextProps.value !== this.props.value
  }

  render() {
    return <h1>{this.props.value}</h1>
  }
}

Aunque sigue siendo preferible usar PureComponent, no sólo por ahorrarnos líneas de código sino por la propia recomendación de la documentación de React.

En esta solución basta con crear un componente de tipo clase con PureComponent y React hace todo el trabajo por nosotros.

El HOC React.memo nos permite optimizar los renderizados sólo cuando los props cambian.

Consideremos el componente Title en forma de componente funcional.

const Title = (value) => <h1>{value}</h1>

Ahora vamos a hacer el wrapper con React.memo para optimizar su renderizado.

const Title = (value) => <h1>{value}</h1>

const TitleWithMemo = React.memo(Title)

// usar como: <TitleWithMemo value="memo" />

Y con esto es suficiente.

Lo importante a destacar aquí es que este hook nos va a ayudar a memorizar valores dentro de un componente.

Recapitulación del hook useMemo

En este post hemos visto lo siguiente:

  • El hook useMemo nos sirve para memorizar valores.
  • Recibe una función que retorna el valor a memorizar y como segundo parámetro opcional, un array de dependencias.
  • Si no pasamos el array de dependencias, se ejecutará en cada renderizado.
  • Especialmente útil para ahorrar cálculos costosos en cada renderizado como vimos en el ejemplo práctico.
  • PureComponet nos crea un componente que dados los mismos props y estado, ahorra el renderizado.
  • React.memo es un HOC que permite optimizar un componente ahorrando los renderizados al tener los mismos props.

Con esto hemos cubierto gran parte del tema para hacer optimizaciones en componentes en React PERO aún nos queda otro tema complementario para optimizar re renders.

Dale un vistazo al hook useCallback para ver a mayor detalle sobre cómo usar este hook al final de este post.

🔔 Bonus: ¿Te gustaría dar un paso al siguiente nivel?

Te dejo links de mis cursos premium de Udemy donde te ayudo a subir tu nivel de seniority 🤓 ☕️.