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.