React v18.0

React 18 is now available on npm! In our last post, we shared step-by-step instructions for upgrading your app to React 18. In this post, we'll give an overview of what's new in React 18, and what it means for the future.

29 de marzo de 2022 por The React Team


¡React 18 ya está disponible en npm! En nuestro último artículo, compartimos instrucciones paso a paso para actualizar tu aplicación a React 18. En este artículo, daremos una descripción general de las novedades en React 18 y lo que significa para el futuro.


Nuestra última versión mayor incluye mejoras listas para usar, como el procesamiento por lotes automático, nuevas API como startTransition y renderizado en el servidor con transmisión con soporte para Suspense.

Muchas de las funcionalidades en React 18 se basan en nuestro nuevo renderizador concurrente, un cambio interno que desbloquea nuevas y poderosas capacidades. React Concurrente es opcional, solo se activa cuando se utiliza una funcionalidad concurrente, pero creemos que tendrá un gran impacto en la forma en que las personas construyen aplicaciones.

Hemos dedicado años a investigar y desarrollar el soporte para la concurrencia en React, y nos hemos esforzado aún más para ofrecer un camino de adopción gradual para los usuarios existentes. El verano pasado, formamos el Grupo de Trabajo de React 18 para recopilar comentarios de expertos en la comunidad y garantizar una experiencia de actualización fluida para todo el ecosistema de React.

En caso de que te lo hayas perdido, compartimos gran parte de esta visión en React Conf 2021.

A continuación se muestra una descripción completa de lo que puedes esperar en esta versión, comenzando con el Renderizado Concurrente.

Nota

Para los usuarios de React Native, React 18 se lanzará en React Native con la Nueva Arquitectura de React Native. Para más información, puedes ver la presentación oficial de la React Conf aquí.

¿Qué es React Concurrente?

La adición más importante en React 18 es algo en lo que esperamos que nunca tengas que pensar: la concurrencia. Creemos que esto es en gran medida cierto para los desarrolladores de aplicaciones, aunque la situación puede ser un poco más complicada para los mantenedores de bibliotecas.

La concurrencia no es una funcionalidad en sí misma. Es un nuevo mecanismo detrás de escena que permite a React preparar múltiples versiones de tu interfaz de usuario al mismo tiempo. Puedes pensar en la concurrencia como un detalle de implementación: es valiosa debido a las funcionalidades que desbloquea. React utiliza técnicas sofisticadas en su implementación interna, como colas de prioridad y múltiples búferes. Pero no verás esos conceptos en ninguna de nuestras APIs públicas.

Cuando diseñamos las APIs, tratamos de ocultar los detalles de implementación a los desarrolladores. Como desarrollador de React, te enfocas en cómo quieres que sea la experiencia del usuario y React se encarga de cómo ofrecer esa experiencia. Por lo tanto, no esperamos que los desarrolladores de React sepan cómo funciona la concurrencia bajo el capó.

Sin embargo, React Concurrente es más importante que un simple detalle de implementación, es una actualización fundamental en el modelo de renderizado central de React. Entonces, aunque no es muy importante saber cómo funciona la concurrencia, puede valer la pena saber qué es a grandes rasgos.

Una propiedad clave de React Concurrente es que el renderizado es interrumpible. Cuando actualizas por primera vez a React 18, antes de agregar cualquier característica concurrente, las actualizaciones se renderizan de la misma manera que en versiones anteriores de React: en una transacción única, sin interrupciones y sincrónica. Con el renderizado sincrónico, una vez que comienza el renderizado de una actualización, nada puede interrumpirlo hasta que el usuario pueda ver el resultado en la pantalla.

En un renderizado concurrente, esto no siempre es el caso. React puede comenzar a renderizar una actualización, pausar en el medio y luego continuar más tarde. Incluso puede abandonar por completo un renderizado en progreso. React garantiza que la interfaz de usuario se mantendrá consistente incluso si se interrumpe un renderizado. Para lograr esto, espera para realizar mutaciones en el DOM hasta el final, una vez que se ha evaluado todo el árbol. Con esta capacidad, React puede preparar nuevas pantallas en segundo plano sin bloquear el hilo principal. Esto significa que la interfaz de usuario puede responder de inmediato a la entrada del usuario incluso si se encuentra en medio de una tarea de renderizado grande, creando una experiencia de usuario fluida.

Otro ejemplo es el estado reutilizable. React Concurrente puede eliminar secciones de la interfaz de usuario de la pantalla y luego agregarlas nuevamente más tarde, reutilizando el estado anterior. Por ejemplo, cuando un usuario cambia de pestaña en una pantalla y vuelve, React debería poder restaurar la pantalla anterior en el mismo estado en el que se encontraba antes. En una próxima versión menor, planeamos agregar un nuevo componente llamado <Offscreen> que implementa este patrón. Del mismo modo, podrás utilizar Offscreen para preparar una nueva interfaz de usuario en segundo plano para que esté lista antes de que el usuario la revele.

El renderizado concurrente es una poderosa herramienta nueva en React y la mayoría de nuestras nuevas características están diseñadas para aprovecharla, incluyendo Suspense, transiciones y renderizado en tiempo real en el servidor. Pero React 18 es solo el comienzo de lo que buscamos construir sobre esta nueva base.

Adoptando gradualmente las funcionalidades concurrentes

Técnicamente, el renderizado concurrente es un cambio disruptivo. Debido a que el renderizado concurrente es interrumpible, los componentes se comportan de manera ligeramente diferente cuando está habilitado.

En nuestras pruebas, hemos actualizado miles de componentes a React 18. Lo que hemos encontrado es que casi todos los componentes existentes «simplemente funcionan» con el renderizado concurrente, sin necesidad de realizar cambios. Sin embargo, algunos de ellos pueden requerir un esfuerzo adicional de migración. Aunque los cambios suelen ser pequeños, aún tienes la capacidad de hacerlos a tu propio ritmo. El nuevo comportamiento de renderizado en React 18 solo se activa en las partes de tu aplicación que utilizan nuevas funcionalidades.

La estrategia general de actualización consiste en hacer que tu aplicación funcione con React 18 sin romper el código existente. Luego, puedes comenzar gradualmente a agregar funcionalidades concurrentes a tu propio ritmo. Puedes utilizar <StrictMode> para ayudar a detectar errores relacionados con la concurrencia durante el desarrollo. Strict Mode no afecta el comportamiento en producción, pero durante el desarrollo mostrará advertencias adicionales y duplicará la invocación de funciones que se espera que sean idempotentes. No detectará todo, pero es efectivo para prevenir los errores más comunes.

Después de actualizar a React 18, podrás comenzar a utilizar funcionalidades concurrentes de inmediato. Por ejemplo, puedes utilizar startTransition para navegar entre pantallas sin bloquear la entrada del usuario. O usar useDeferredValue para limitar la frecuencia de las costosas re-renderizaciones.

Sin embargo, a largo plazo, esperamos que la forma principal de agregar concurrencia a tu aplicación sea utilizando una biblioteca o marco de trabajo compatible con la concurrencia. En la mayoría de los casos, no interactuarás directamente con las APIs concurrentes. Por ejemplo, en lugar de que los desarrolladores llamen a startTransition cada vez que naveguen a una nueva pantalla, las bibliotecas de enrutamiento envolverán automáticamente las navegaciones en startTransition.

Puede llevar algún tiempo que las bibliotecas se actualicen para ser compatibles con la concurrencia. Hemos proporcionado nuevas APIs para facilitar a las bibliotecas aprovechar las funcionalidades concurrentes. Mientras tanto, te pedimos paciencia con los mantenedores mientras trabajamos en la migración gradual del ecosistema de React.

Para obtener más información, consulta nuestra publicación anterior: Como actualizar a React 18.

Suspense en Frameworks de datos

En React 18, puedes comenzar a usar Suspense para la obtención de datos en frameworks frecuentes como Relay, Next.js, Hydrogen, or Remix. La obtención ad hoc de datos con Suspense es técnicamente posible, pero aún no se recomienda como una estrategia general.

En el futuro, es posible que expongamos primitivas adicionales que faciliten el acceso a tus datos con Suspense, tal vez sin necesidad de utilizar un marco de trabajo opinionado. Sin embargo, Suspense funciona mejor cuando está profundamente integrado en la arquitectura de tu aplicación: en tu enrutador, en tu capa de datos y en tu entorno de renderizado en el servidor. Por lo tanto, incluso a largo plazo, esperamos que las bibliotecas y marcos de trabajo desempeñen un papel crucial en el ecosistema de React.

Al igual que en versiones anteriores de React, también puedes utilizar Suspense para el fragmento de código en el cliente con React.lazy. Pero nuestra visión de Suspense siempre ha sido mucho más que cargar código; el objetivo es ampliar el soporte para Suspense para que eventualmente, el mismo fallback declarativo de Suspense pueda manejar cualquier operación asíncrona (carga de código, datos, imágenes, etc.)

Server Components está aún en desarrollo

Server Components es una funcionalidad próxima que permite a los desarrolladores crear aplicaciones que abarcan el servidor y el cliente, combinando la interactividad completa de las aplicaciones del lado del cliente con el rendimiento mejorado del renderizado tradicional en el servidor. Server Components no está inherentemente vinculado a Concurrent React, pero está diseñado para funcionar mejor con funcionalidades concurrentes como Suspense y el renderizado en el servidor en streaming.

Server Components todavía está en fase experimental, pero esperamos lanzar una versión inicial en una versión menor de 18.x. Mientras tanto, estamos trabajando con frameworks como Next.js, Hydrogen y Remix para avanzar en la propuesta y prepararla para su adopción generalizada.

Que hay de nuevo en React 18

Nueva función: Agrupación automática

La agrupación (batching) es cuando React agrupa múltiples actualizaciones de estado en una sola re-renderización para mejorar el rendimiento. Sin la agrupación automática, solo se agrupaban las actualizaciones dentro de los controladores de eventos de React. Las actualizaciones dentro de promesas, setTimeout, controladores de eventos nativos u otros eventos no se agrupaban automáticamente en React por defecto. Con la agrupación automática, estas actualizaciones se agruparán automáticamente:

// Antes: solo los eventos de React se agrupaban.
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React se renderizará dos veces, una vez por cada actualización de estado (sin agrupación).
}, 1000);

// Después: las actualizaciones dentro de timeouts, promesas,
// controladores de eventos nativos o cualquier otro evento se agrupan.
setTimeout(() => {
setCount(c => c + 1);
setFlag(f => !f);
// React solo se volverá a renderizar una vez al final (¡eso es agrupación!).
}, 1000);

Para más información, consulta este artículo sobre la agrupación automática para reducir las re-renderizaciones en React 18.

Nueva funcionalidad: Transiciones

Una transición es un nuevo concepto en React para distinguir entre actualizaciones urgentes y no-urgentes.

  • Actualizaciones urgentes reflejan interacción directa, como escribir, hacer click, presionar, y así sucesivamente.
  • Actualizaciones de transición permiten la transición de la interfaz de usuario de una vista a otra.

Las actualizaciones urgentes como escribir, hacer clic o presionar requieren una respuesta inmediata para coincidir con nuestras intuiciones sobre cómo se comportan los objetos físicos. De lo contrario, se sienten «incorrectas». Sin embargo, las transiciones son diferentes porque el usuario no espera ver cada valor intermedio en la pantalla.

Por ejemplo, cuando seleccionas un filtro en un menú desplegable, esperas que el propio botón del filtro responda de inmediato cuando haces clic. Sin embargo, los resultados reales pueden transicionar por separado. Un pequeño retraso sería imperceptible y a menudo esperado. Y si cambias el filtro nuevamente antes de que los resultados terminen de renderizarse, solo te importará ver los últimos resultados.

Típicamente, para tener la mejor experiencia de usuario, una única entrada del usuario debería resultar en una actualización urgente y otra no urgente. Puedes utilizar la API startTransition dentro de un evento de entrada para informar a React qué actualizaciones son urgentes y cuáles son «transiciones»:

import { startTransition } from 'react';

// Urgente: Mostrar lo que se escribió
setInputValue(input);

// Marcar cualquier actualización de estado dentro como transiciones
startTransition(() => {
// Transición: muestra el resultado
setSearchQuery(input);
});

Las actualizaciones envueltas en startTransition se manejan como no urgentes y se interrumpirán si llegan actualizaciones más urgentes, como clics o pulsaciones de teclas. Si una transición se interrumpe por el usuario (por ejemplo, al escribir varios caracteres seguidos), React descartará el trabajo de renderizado obsoleto que no se completó y solo renderizará la última actualización.

  • useTransition: un Hook para iniciar transiciones, que incluye un valor para rastrear el estado pendiente.
  • startTransition: un método para iniciar transiciones cuando no se puede utilizar el Hook.

(Las transiciones optarán por el renderizado concurrente, lo cual permite interrumpir la actualización. Si el contenido se suspende de nuevo, las transiciones también indican a React que continúe mostrando el contenido actual mientras renderiza el contenido de la transición en segundo plano. Consulta Suspense RFC para obtener más información).

Consulta la documentación de transiciones aquí.

Nuevas funcionalidades de Suspense

Suspense te permite especificar declarativamente el estado de carga para una parte del árbol de componentes si aún no está listo para mostrarse:

<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>

Suspense convierte el «estado de carga de la interfaz de usuario» en un concepto declarativo de primera clase en el modelo de programación de React. Esto nos permite construir funcionalidades de nivel superior sobre él.

Introdujimos una versión limitada de Suspense hace varios años. Sin embargo, el único caso de uso compatible era la división de código con React.lazy, y no se admitía en absoluto al renderizar en el servidor.

En React 18, hemos agregado soporte para Suspense en el servidor y hemos ampliado sus capacidades utilizando funciones de renderizado concurrente.

Suspense en React 18 funciona mejor cuando se combina con la API de transición. Si suspendes durante una transición, React evitará que el contenido ya visible sea reemplazado por un respaldo. En su lugar, React retrasará el renderizado hasta que se haya cargado suficiente información para evitar un mal estado de carga.

Para más información, consulta el RFC para Suspense en React 18.

Nuevas APIs de renderizado en cliente y servidor

En esta versión, aprovechamos la oportunidad para rediseñar las API que exponemos para el renderizado en el cliente y en el servidor. Estos cambios permiten a los usuarios seguir utilizando las antiguas API en el modo React 17 mientras actualizan a las nuevas API en React 18.

Cliente React DOM

Estas nuevas API son exportadas desde react-dom/client:

  • createRoot: Nuevo metodo para crear un root para render(renderizar) o unmount (desmontar). Usalo en lugar de ReactDOM.render. Las nuevas funcionalidades en React 18 no funcionan sin él.
  • hydrateRoot: Nuevo método para hidratar una aplicación renderizada en el servidor. Úsalo en lugar de ReactDOM.hydrate en conjunto con las nuevas API de React DOM Server. Las nuevas características en React 18 no funcionan sin él.

Tanto createRoot como hydrateRoot aceptan una nueva opción llamada onRecoverableError en el caso de que necesites ser notificado cuando React recupere errores durante el renderizado o la hidratación para el registro. Por defecto, React usará reportError, o console.error en navegadores antiguos.

Consulta la documentación de Cliente React DOM aquí.

Servidor React DOM

Estas nuevas APIs son exportadas desde react-dom/server y tienen soporte completo para Suspense en el servidor:

  • renderToPipeableStream: para transmisión en entornos de Node
  • renderToReadableStream: para entornos de tiempo de ejecución modernos, como Deno y Cloudflare workers.

El método existente renderToString sigue en funcionamiento, pero se desaconseja su uso.

Consulta la documentación de Servidor React DOM aquí.

Nuevos Comportamientos del Modo Estricto

En el futuro, nos gustaría agregar una función que permita a React agregar y eliminar secciones de la interfaz de usuario mientras se preserva el estado. Por ejemplo, cuando un usuario cambia de pestaña y vuelve, React debería poder mostrar inmediatamente la pantalla anterior. Para lograr esto, React desmontaría y volvería a montar los árboles utilizando el mismo estado del componente como antes.

Esta funcionalidad proporcionará un mejor rendimiento por defecto en las aplicaciones de React, pero requiere que los componentes sean resistentes a que los efectos se monten y destruyan varias veces. La mayoría de los efectos funcionarán sin cambios, pero algunos efectos asumen que solo se montan o destruyen una vez.

Para ayudar a detectar estos problemas, React 18 introduce una nueva verificación exclusiva para el modo estricto en el entorno de desarrollo. Esta nueva verificación desmontará y volverá a montar automáticamente cada componente cuando se monte por primera vez, restaurando el estado anterior en el segundo montaje.

Antes de este cambio, React montaba el componente y creaba los efectos:

* React monta el componente.
* Se crean los efectos de diseño (layout effects).
* Se crean los efectos.

Con el modo estricto (Strict Mode) en React 18, React simulará el desmontaje y remontaje del componente en modo de desarrollo:

* React monta el componente.
* Se crean los efectos de diseño (layout effects).
* Se crean los efectos.
* React simula el desmontaje del componente.
* Se destruyen los efectos de diseño (layout effects).
* Se destruyen los efectos.
* React simula el montaje del components con el estado anterior.
* Se crean los efectos de diseño (layout effects).
* Se crean los efectos.

Consulta la documentación sobre cómo garantizar el estado reutilizable aquí.

Nuevos Hooks

useId

useId es un nuevo Hook para generar IDs únicos tanto en el cliente como en el servidor, evitando desajustes en la hidratación. Es especialmente útil para bibliotecas de componentes que se integran con API de accesibilidad que requieren identificadores únicos. Esto resuelve un problema que ya existe en React 17 y versiones anteriores, pero es aún más importante en React 18 debido a cómo el nuevo renderizador de servidor en streaming entrega el HTML sin un orden específico. Consulta la documentación.

Note

useId no está diseñado para generar llaves en una lista. Las llaves deben ser generadas a partir de tus datos.

useTransition

useTransition y startTransition te permiten marcar algunas actualizaciones de estado como no urgentes. Otras actualizaciones de estado se consideran urgentes de manera predeterminada. React permitirá que las actualizaciones de estado urgentes (por ejemplo, actualizar un campo de texto) interrumpan las actualizaciones de estado no urgentes (por ejemplo, renderizar una lista de resultados de búsqueda). Consulta la documentación aquí.

useDeferredValue

useDeferredValue te permite posponer el re-renderizado de una parte no urgente del árbol. Es similar a la técnica de debounce, pero tiene algunas ventajas en comparación con esta. No hay un retraso de tiempo fijo, por lo que React intentará el renderizado diferido justo después de que el primer renderizado se refleje en la pantalla. El renderizado diferido es interrumpible y no bloquea la entrada del usuario. Consulta la documentación aquí.

useSyncExternalStore

useSyncExternalStore es un nuevo Hook que permite a las tiendas externas admitir lecturas concurrentes al forzar que las actualizaciones de la tienda sean síncronas. Elimina la necesidad de usar useEffect al implementar suscripciones a fuentes de datos externas, y se recomienda para cualquier biblioteca que se integre con un estado externo a React. Consulte la documentación aquí.

Note

useSyncExternalStore está destinado a ser utilizado por bibliotecas, no por el código de aplicación.

useInsertionEffect

useInsertionEffect es un nuevo Hook que permite a las bibliotecas de CSS-in-JS abordar problemas de rendimiento al inyectar estilos durante el renderizado. A menos que ya hayas construido una biblioteca de CSS-in-JS, no esperamos que nunca lo utilices. Este Hook se ejecutará después de que el DOM haya sido mutado, pero antes de que los efectos de diseño (layout effects) lean el nuevo diseño. Esto resuelve un problema que ya existe en React 17 y versiones anteriores, pero es aún más importante en React 18 porque React cede al navegador durante el renderizado concurrente, dándole la oportunidad de recalcular el diseño. Consulta la documentación aquí.

Note

useInsertionEffect está destinado a ser utilizado por bibliotecas, no por el código de aplicación.

Cómo Actualizar

Consulta Cómo Actualizar a React 18 para obtener instrucciones paso a paso y una lista completa de cambios significativos y trascendentales.

Changelog (Registro de Cambios)

React

React DOM

Servidor React DOM

Utilidades de Pruebas React DOM

  • Lanzar error cuando se usa act en producción. (#21686 por @acdlite)
  • Soporte para deshabilitar las advertencias espurias con global.IS_REACT_ACT_ENVIRONMENT. (#22561 por @acdlite)
  • Expandir las advertencias act para cubrir todas las APIs que podrían crear trabajo en React. (#22607 por @acdlite)
  • Hacer agrupamientos de actualizaciones act. (#21797 por @acdlite)
  • Remover advertencias por effectos pasivos colgados. (#22609 por @acdlite)

React Refresh

  • Hacer seguimiento de los roots montados tarde en Fast Refresh. (#22740 por @anc95)
  • Añadir el campo exports en package.json. (#23087 por @otakustay)

Componentes del Servidor (Experimental)