Mejorando el rendimiento de tu Aplicacion React Js
A menudo, cuando desarrollamos algún mvp o una app simple como un ecommerce te topas con trabas de performance, es por eso que me nació la idea de investigar a fondo que hay de bajo del capo de react js, es decir ‘behind the scenes’, en este articulo abarcare los siguientes temas:
- Prevenir/Evitar la ejecución de código innecesario a traves de useMemo() y useCallback().
- Cargar código solo cuando sea necesario, a traves de lazy()
Tiene sentido revisar brevemente la lógica de React para ejecutar componentes de función (functional components).
Cada vez que se ejecuta un ‘functional component’, react evalúa si la ui renderizada (es decir, el DOM de la pagina cargada) debe actualizarse o no.
A lo que voy con esto es que React evalúa si se necesita una actualización, No forza una actualización automáticamente.
Si el DOM actual de la página se sustituyera por los elementos del DOM implícitos en el código JSX devuelto, el resultado visual sería siempre el mismo. Pero seguiría habiendo cierta manipulación del DOM. Y eso es un problema, porque manipular el DOM es una tarea que consume mucho rendimiento, especialmente cuando se hace con mucha frecuencia. Por lo tanto, eliminar, añadir o actualizar elementos del DOM sólo debe hacerse cuando sea necesario, no innecesariamente.
Debido a esto, React no tira el DOM actual y lo reemplaza con el nuevo DOM, sólo porque se haya ejecutado una función del componente. En su lugar, React primero comprueba si es necesaria una actualización. Y si es necesaria, sólo las partes del DOM que necesitan cambiar son reemplazadas o actualizadas.
Para determinar si se necesita una actualización (y dónde), React utiliza un concepto llamado DOM virtual.
Proceso por Lotes.
El procesamiento por lotes de estado es cuando se inician multiples actualizaciones de estado desde el mismo lugar en su código(por ejemplo, desde la misma función de controlador de eventos).
Esta funcionalidad garantiza que las funciones de sus componentes no se llamen con mas frecuencia de la necesaria.
La dosificación de estados es un mecanismo muy util. Pero existe otro tipo de evaluación innecesaria de componentes que no evita: las funciones de componentes de hijos que se ejecutan cuando se llama a la función del componente padre.
Este es un ejemplo de código a lo que me refiero con lo anterior:
function App() {
const [counter, setCounter] = useState(0)
const [viewCounter, setViewCounter] = useState(false)
function incCounterHandler() {
setCounter((prevCounter) => prevCounter + 1)
if (!viewCounter) {
setViewCounter(true)
}
}
return (
<>
<p>Click to increment + show or hide the counter</p>
<button onClick={incCounterHandler}>Increment</button>
{viewCounter && <p>Counter: {counter}</p>}
</>
)
}
Este componente contiene dos valores de estado: counter y viewCounter. Cuando se pulsa el botón, el contador se incrementa en 1. Ahora, viewCounter se establece en true si estaba establecido en false. Por lo tanto, la primera vez que se pulsa el botón, cambian tanto el estado del contador como el de viewCounter (porque viewCounter es falso inicialmente).
Este comportamiento se denomina procesamiento por lotes de estado. React realiza el procesamiento por lotes de estado cuando se inician múltiples actualizaciones de estado desde el mismo lugar en su código (por ejemplo, desde dentro de la misma función de controlador de eventos), como lo mencionaba anteriormente.
La dosificación de estados es un mecanismo muy util. Pero existe otro tipo de evaluación innecesaria de componentes que no evita: las funciones de componentes de hijos que se ejecutan cuando se llama a la función del componente padre.
Prevenir evaluaciones innecesarias de componentes secundarios
Cuando los componentes están anidados, uno con el otro es decir comparten el estado, es posible que no dependan del valor del estado que cambio en el componente principal, incluso que ni siquiera dependan de ningún valor del componente padre.
La definición de lo que les hablo es esto, aquí les mostrare un componente principal que contiene algún estado que no utiliza el componente hijo.
//Error.jsx
const Error = ({ message }) => {
if (!message) {
return null
}
return <p className={classes.error}>{message}</p>
}
export default Error
//Form.jsx
import { useState } from 'react'
import Error from './components/Error'
const Form = () => {
const [email, setEmail] = useState('')
const [errorMessage, setErrorMessage] = useState()
function updateEmail(event) {
setEmail(event.target.value)
}
function submitHandler(event) {
event.preventDefault()
if (!enteredEmail.endsWith('.com')) {
setErrorMessage('Email must end with .com.')
}
}
return (
<form onSubmit={submitHandler}>
<div>
<label htmlFor="email">Email</label>
<input id="email" type="email" value={email} onChange={updateEmail} />
</div>
<Error message={errorMessage} />
<button>Sign Up</button>
</form>
)
}
En este ejemplo vemos como el componente Form, almacena dos valores de estado, por lo cual el componente Error solo recibe mediante props el message, esto trae como consecuencia la necesidad de administrar el otro estado que seria email. Por lo tanto, los cambios en el email estado harán que el componente Error se ejecute nuevamente, a pesar de que el componente no necesita el valor.
Para dar seguimiento a este error, que prácticamente es una invocación de función innecesaria, solo agregue un console.log().
// Error.jsx
const Error = () => {
console.log('<Error /> component executed')
if (!message) return null
return <p>{message}</p>
}
Cada que pulsamos una tecla se ejecuta el componente Error.

32 ejecuciones con solo escribir un email.
En la imagen pasada vemos un claro ejemplo de la fuga de errores que nos esta mandando el componente Error, es decir cada vez que cambia el email estado en el componente padre.
El componente de Error, depende del errorMessage estado y debe evaluarse cada vez que ese estado cambie, pero claramente no es necesario ejecutar el Error del componente porque el valor de email estado se actualizó.
Ya que tenemos en mente el Error es momento de dar introducción al concepto de memo, que nos ayudara a controlar y prevenir este comportamiento.
La sintaxis de memo(), es la siguiente:
memo(someComponent, props)
- El componente que desea memorizar, el memo no modifica este componente sino que devuelve un nuevo componente memorizado en su lugar.
- props(opcional): una función que acepta dos argumentos.
A menudo el segundo argumento no es necesario, ya que el comportamiento de memo()(compara todos los props para la desigualdad.) es exactamente lo que necesita.
memo: le permite omitir renderizaciones de su componente cuando sus props no cambian.
Esta técnica es solo una optimización de rendimiento, no una garantía, todo depende del contexto en el que se encuentre.
import { memo } from 'react'
const Error = ({ message }) => {
console.log('<Error /> component executed')
if (!message) return null
return <p>{message}</p>
}
export default memo(Error)
Al memorizar el valor, tenemos como resultado…

Evitar Cálculos costosos.
El hook memo() puede ayudar a evitar ejecuciones innecesarias de funciones de componentes, lo cual es especialmente valioso si una función de componente realiza mucho trabajo.
En algunas situaciones, es necesario ejecutar una tarea intensiva de trabajo de nuevo debido a un cambio en alguna propiedad del componente. En estos casos, el uso de memo() no evitará que se vuelva a ejecutar la función del componente. Sin embargo, es posible que el cambio en la propiedad no sea necesario para la tarea de alto rendimiento que se realiza como parte del componente.