React v16.6: lazy, memo y más
Salió React v16.6, y con este vienen varias novedades, entre ellas el lanzamiento de la primera parte de React Suspense mediante una nueva función llamada lazy
y de otra función para evitar doble renders llamada memo
.
React.memo
: Evitando doble renders
Esta función nos permite memoizar el render de un componente en base a sus props
y evitar hacer otro render si estos no cambiaron. Esto ya era posible extendiendo de PureComponent
, pero hacerlo así significaba crear sí o sí una clase con el consiguiente overhead al rendimiento y dificultar optimizaciones posibles sobre las funciones.
Esta nueva función entonces nos va a permitir memoizar un componente tanto creado como una clase como usando funciones. Incluso se puede hacer memoize del resultado de React.lazy
.
import React, { memo } from "react";
import logo from "./logo.svg";
function Logo({ alt }) {
return <img src={logo} className="App-logo" alt={alt} />;
}
export default memo(Logo);
Como vemos creamos el componente de forma normal y se lo pasamos a React.memo
, este entonces devuelve el nuevo componente memoizado que podemos exportar.
Adicionalmente es posible pasar un segundo argumento a React.memo
para personalizar la forma en que valida si cambiaron los props ya que por defecto hace un shallow equal de todos los props.
export default memo(Logo, (prevProps, nextProps) => {
return prevProps.alt === nextProps.alt;
});
En este caso React.memo
solo va a dejar que Logo
se vuelva a renderizar si el prop alt
cambió, pero si cualquier otro prop cambia se va a ignorar. Esto es similar a usar el método del ciclo de vida shouldComponentUpdate
con una particularidad de que funciona a la inversa, se debe devolver true
si el componente va a dar el mismo resultado y false
si da diferente resultado, lo que significa que nuestra función no debe verificar si el componente debe actualizarse sino si los props son iguales.
Nota: La razón de llamarse
memo
es pormemoize
, se usa esta forma corta para evitar errores comunes al escribir la palabramemoize
. No se llamapure
como fue inicialmente ideado o comoPureComponent
debido a que no asegura la pureza del componente (que no tenga side effects), solamente que memoiza el resultado.
React.lazy
: Code Split con Suspense
Esta nueva función, que se incorpora al core de React, permite hacer code split y lazy load de componente de React. Algo que hasta ahora era posible usando librerías como react-loadable
o next/dynamic
(de Next.js).
Esta función es simple de usar, recibe como único argumento una función asíncrona que devuelve una promesa de importar un componente de React. Dentro de esta función es posible agregar más lógica.
import { lazy } from "react";
import sleep from "sleep";
const Logo = lazy(async () => {
await sleep(1000);
return import("./logo.js");
});
En este caso el componente Logo
que devuelve lazy
va a esperar un segundo y recién entonces hacer import
de nuestro componente ./logo.js
. El sleep
en este caso nos permite finjir una carga lenta para probar que efectivamente el componente esta siendo cargando de forma asíncrona.
El import
funciona gracias al module bundler que uses, ya sea webpack, Parcel, Rollup o cualquier otro, estos van a crear un split point donde se use esta función y van a encargarse de que se cargue asíncronamente el módulo ./logo.js
cuando se ejecute dicha función.
Nota: Este método todavía no funciona en el servidor, en futuras versiones va a estar disponible. Esto significa que no es posible usarlo con Next.js o SSR en general.
React.Suspense
Este componente esta relacionado con lazy
y es obligatorio usarlo si usamos lazy load, en caso de no usarlo React muestra un error diciendo que es necesario.
Lo que hace Suspense
es simple, envuelve nuestro componente lazy en otro componente y renderiza un fallback si es que todavía no se terminó de cargar el componente lazy.
import React, { Component, Suspense } from "react";
import LazyLogo from "./lazy-logo.js"; // nuestro componente lazy
import Placeholder from "./placeholder.js"; // un componente que sirva de placeholder
class App extends Component {
state = {
alt: "React"
};
render() {
return (
<Suspense maxDuration={300} fallback={<Placeholder />}>
<LazyLogo alt={this.state.alt} />
</Suspense>
);
}
}
Ahora cuando App
sea renderizado este va a pasar su estado a LazyLogo
y por consiguiente al componente de Logo
, mientras el logo este siendo importado Suspense
va a renderizar el componente Placeholder
que pasamos con el prop fallback
, este componente puede ser tanto algo genérico como un spinner o algo único para nuestro componente lazy.
Por último, el prop maxDuration
nos permite indicar cuanto debe esperar Suspense
antes de renderizar el fallback, esto nos sirve para que si un componente carga lo suficientemente rápido no rendericemos el fallback y evitemos que este se vea durante menos de un segundo.
Nota: el prop
maxDuration
solo funciona cuando el modo concurrente, antes llamado async, de React está habilitado, actualmente este modo no es estable por lo quemaxDuration
es simplemente ignorado.
static contextType
: Accediendo al contexto más fácil
Con React 16.3 se introdujo el API estable para usar contexto, usando React.createContext
.
import { createContext } from "react";
export default createContext();
Esta API, aunque práctica, solo permite usar el contexto en el método render de un componente. Lo que en componentes que son funciones no causa problemas, pero en clases que extienden Component
o PureComponent
impide su uso en el ciclo de vida.
Desde ahora hay otra forma de usar el contexto mediante static propTypes
en una clase.
import { Component } from "react";
import MyContext from "./context.js"; // el archivo que creamos antes
class MyComponent extends React.Component {
static contextType = MyContext;
componentDidMount() {
const value = this.context;
// hacer algo con el contexto acá
}
componentDidUpdate() {
const value = this.context;
// hacer algo con el contexto
}
componentWillUnmount() {
const value = this.context;
// hacer algo con el contexto
}
render() {
const value = this.context;
// user el contexto para hacer render
}
}
export default MyComponent;
Como vemos, con pasar el contexto que devuelve React.createContext
es suficiente para empezar a usarlo en cualquier parte del ciclo de vida de un componente.
Nota: Usando este método solo es posible suscribirse a un contexto a la vez, para usar varios contextos se debería envolver a nuestro componente de clase en una función que use el API actual para pasar el contexto como prop.
static getDerivedStateFromError()
: Reaccionando a los errores antes del render
En React v16 se introdujo también una forma de atrapar errores que ocurren en el render usando el método del ciclo de vida componentDidCatch
. Este método se ejecuta cuando un render tira un errores y nos permite actualizar el estado reaccionar al error de alguna forma en nuestra UI.
Antes de que se cambie el estado React por defecto renderiza null
, lo que en algunos casos puede romper al componente padre del que dio error si este no espera que falte alguna ref. Este método tampoco funciona al renderizar en el servidor ya que todos los métodos que se llaman Did
se ejecutan solo en el navegador.
Desde ahora se puede usar el nuevo método estático getDerivedStateFromError()
para obtener el error antes del render.
Nota: Este método todavía no funciona en el servidor, en futuras versiones va a estar disponible. Esto significa que no es posible usarlo con Next.js o SSR en general.
class ErrorBoundary extends React.Component {
state = {
hasError: false
};
static getDerivedStateFromError(error) {
// retorna los nuevos cambios al estado
return { hasError: true };
}
render() {
if (this.state.hasError) {
// Renderizamos algo en lugar del contenido si hay un error
return <h1>Something went wrong.</h1>;
}
// renderizamos nuestro contenido
return this.props.children;
}
}
El uso es igual que con componentDidCatch
, se usa ErrorBoundary
para envolver un componente y todos los errores que ocurran en sus componentes hijos serían atrapados por getDerivedStateFromError
y nos permitiría reaccionar a este error.
Nuevas advertencias en StrictMode
En la versión 16.3 se introdujo un modo estricto a React que se puede usar envolviendo nuestra aplicación con el componente React.StrictMode
.
Esta nueva versión incluye nuevas advertencias de funcionalides que se van a volver obsoletas en el futuro.
ReactDOM.findDOMNode()
. Esta API se va a eliminar en el futuro, si nunca lo usaste se puede ignorar, si lo usaste hay una guía en la documentación explicando como actualizar.- Antíguo API de contexto usando
contextTypes
ygetChildContext
. El antíguo API de contexto vuelve a React más lento y pesado de lo que debería ser. La recomendación es actualizar al nuevo API para que en el futuro se pueda eliminar el soporte al API viejo.
Palabras finales
Como se ve esta nueva versión trae muchas cosas interesantes al ecosistema de React que en su mayoría se resolvían mediante librarías externas y ahora va a ser posible hacer solo con React.
Ya sea que evitemos renders innecesarios en un componente de función usando memo
o carguemos de forma asíncrona usando lazy
de a poco React nos va dando más y más herramientas para crear una mejor experiencia de usuario de forma más simple para los desarrolladores.
Por último si quieren ver como funciona lazy
y memo
pueden ver una demo en https://react-lazy-memo.now.sh y el código fuente en https://github.com/sergiodxa/react-lazy-memo.