Reaccionar shouldComponentUpdate desmitificado

Mientras desarrollaba en React, ¿alguna vez se preguntó cuándo y por qué se ejecuta el método render() de un componente? ¿O cuándo usar métodos de ciclo de vida menos obvios shouldComponentUpdate()?

Si la respuesta es sí, es posible que su aplicación tenga problemas de rendimiento. Lea y podrá solucionarlos fácilmente.

Todo se reduce a cómo funciona React bajo el capó. La gran promesa de React es que es increíblemente rápido en la representación de elementos en una página.

Para ello React guarda en memoria dos versiones del DOM:

  • la versión del DOM que se muestra actualmente
  • la próxima versión del DOM que se mostrará

Compara los dos y actualiza el DOM mostrado con solo las partes que han cambiado. Este proceso se denomina reconciliación de árboles. La raíz del árbol evaluado para la reconciliación es un componente cuyas propiedades han cambiado.

Excelente. Ahora, ya sea que lo haya planeado o no, su aplicación web sigue la división de componentes de presentación/contenedor hasta cierto punto. Ver aquí y aquí para definiciones. Esto significa que cada vista compleja en su aplicación está hecha de un componente de contenedor que contiene la lógica y tiene muchos componentes de solo visualización como elementos secundarios.

Este es un patrón muy bueno. Sin embargo, si mira más de cerca, significa que cualquier interacción del usuario en la vista afectará al contenedor en sí y activará una representación de él y todos sus elementos secundarios. Supongamos que tiene una lista de elementos con una pantalla elegante de texto, imagen y un botón de estrella amarilla "Agregar a favoritos". El modelo mínimo para un elemento de lista podría ser:

product = { 
    imageUrl: '...', 
    title: '...', 
    isFavourite: false
}

La lista de favoritos podría provenir de otra fuente de datos. Independientemente, la organización de sus componentes probablemente se vea así:

<Container>
    <ListOfElements
        elements={this.props.elements} 
        onElementChanged={this.props.onElementChanged} 
    />
</Container>

Se llama al controlador cuando el usuario hace clic y guarda el lado del servidor de información (o persiste en una tienda o lo que sea) y activa un cambio en this.props.elements.

El resultado de un solo clic desencadena el procesamiento del contenedor y de todas las filas de la lista solo para actualizar una casilla de verificación.

Aquí es donde entra en juego shouldComponentUpdate(). Puede decirle a React que no represente las filas que no necesitan usar este método.

class ListItem extends Component {
    shouldComponentUpdate(nextProps, nextState) {
        return nextProps.isFavourite != this.props.isFavourite;
    }
    ...
}

Aquí hay un caso concreto:en un proyecto de aplicación de mercado, teníamos una vista de administración de productos para los vendedores. La lista tenía un patrón de "cargar más a medida que el usuario se desplaza hacia abajo" y acciones de elementos en línea "mostrar/ocultar" para establecer la visibilidad de cada producto. Todo estaba bien cuando los vendedores administraban <100 productos en su tablero. Entonces un determinado vendedor empezó a entrar y publicitar más de 300 productos…

Hubo un retraso de ~600 ms antes de que la interfaz de usuario se actualizara después de que un usuario hiciera clic en el ícono "habilitar/deshabilitar". El retraso fue definitivamente visible para el usuario final. Usando el generador de perfiles de Chrome, vimos que React tardó ~ 2 ms en renderizar una sola fila. Veces 300... llegamos a 600ms. Agregamos las comprobaciones shouldComponentUpdate() para las condiciones adecuadas. El tiempo de procesamiento después de que el usuario hizo clic fue inferior a 10 ms...

He elaborado un pequeño proyecto que permite reproducir este caso aquí. Ejecútelo y lea los comentarios del código para ver cómo sucede la magia.

Advertencia para usuarios de Redux

El problema descrito anteriormente puede ocurrir con más frecuencia si está utilizando Redux y vuelve a seleccionar (o bibliotecas de canalizaciones de acción "basadas en la tienda" similares).

Con Redux y volver a seleccionar, envía acciones a la tienda y conecta a los oyentes para almacenar cambios, también conocidos como selectores. Los selectores están disponibles globalmente en la aplicación y, en una aplicación grande, es bastante fácil que muchos componentes se asignen a los mismos selectores. Los cambios en la tienda pueden desencadenar cambios de accesorios y, por lo tanto, renderizaciones que son completamente irrelevantes para algunos componentes.

Aquí está el consejo confuso:no use shouldComponentUpdate() para evitar renderizaciones en tales casos. La lógica dentro de shouldComponentUpdate solo debe mirar lo que es relevante para el componente. Nunca debe anticipar los contextos en los que se usa el componente. La razón es que su código se volvería rápidamente imposible de mantener.

Si tiene este tipo de problemas, significa que la estructura de su tienda es incorrecta o que los selectores no son lo suficientemente específicos. Necesitas llegar a una nueva ronda de modelos.

Recomiendo estas impresionantes pautas repetitivas. Promueve la encapsulación de almacenamiento por contenedor de alto nivel con un área global para las estructuras de datos clave que se extienden por toda la aplicación. Este es un enfoque bastante seguro para evitar errores de modelado de tiendas.

¡Gracias por leer! Si te gustó, presiona el botón de aplausos a continuación. Ayuda a otras personas a ver la historia.