En esta charla aprenderemos qué son los Slots y qué significa para el ecosistema de React, cuál es el estado actual y el futuro.
React Slots: una Nueva Forma de Composición
Video Summary and Transcription
La charla de hoy presenta React Slots, una nueva forma de composición para sistemas de diseño. La forma de configuración proporciona flexibilidad pero puede llevar a casos de uso no controlados y patrones incorrectos. Se creó el RFC de React Slots para abordar las limitaciones del soporte de React para componentes web y slots. Introduce las APIs createHost y createSlot para permitir la composición de componentes y resolver problemas anteriores. React Slots RFC permite estilos flexibles de componentes y la creación de estructuras complejas sin renderizarlas en el navegador.
1. Introducción a React Snots y Diseño de API
Hoy, mi tema es React Snots, una nueva forma de composición. Cada vez que encuentro un nuevo sistema de diseño, lo primero que hago es ver cómo implementan el componente de entrada de texto. La solución es simple. Podemos pasar el nombre de la clase y el estilo al contenedor del componente en su lugar. Similar a WrapRef, podemos introducir indicaciones para el contenedor, indicaciones para la etiqueta y indicaciones para la descripción. Aquí está la API del componente de entrada de texto de Man Time.
¡Hola, es un gran honor estar aquí para compartir con ustedes algunos de mis pensamientos sobre el sistema de diseño! Mi nombre es Gongwu Nie, pueden llamarme Neo. Hoy, mi tema es React Snots, una nueva forma de composición.
Antes de adentrarnos en el tema, me gustaría contar la historia primero. Cuando trabajo en el sistema de diseño, siempre me pregunto, ¿cómo construir componentes flexibles y a prueba de futuro? Para mí, lo más difícil no es crear nuevos componentes o solucionar errores extraños. Es el diseño de la API del componente.
Cada vez que encuentro un nuevo sistema de diseño, lo primero que hago es ver cómo implementan el componente de entrada de texto, porque es uno de los componentes más misteriosos. Permítanme mostrarles. El componente de entrada de texto es simple y directo. Viene con una etiqueta, un campo de entrada y tiene una prueba. Primero agreguemos soporte de accesibilidad. Al construir un componente compartible, por favor no olviden reenviar la referencia. Necesitamos la referencia al campo de entrada para enfocarlo en la validación del formulario. Pero, ¿qué pasa con el contenedor? También necesitamos la referencia al contenedor para adjuntar un popover o tooltip en algunos casos. Bueno, podemos agregar un contenedor para ello.
Pero esperen, ¿han notado un problema potencial con el enfoque actual? ¿Qué sucederá si intentamos agregar márgenes al componente desde otro componente? Aumentará el margen entre la etiqueta, el campo de entrada y la descripción, porque estamos propagando todas las demás indicaciones al elemento de entrada interno. La solución es simple. Podemos pasar el nombre de la clase y el estilo al contenedor del componente en su lugar. ¿Se ve bien? El problema es que el componente se vuelve confuso para los consumidores. ¿Qué indicación se pasará al contenedor? ¿Qué indicación se pasará al campo de entrada? El componente se convierte en una caja negra. En realidad, el equipo de producto seguirá pidiendo más opciones para acceder a los elementos internos, como adjuntar eventos de punto o agregar atributos de datos a la etiqueta o descripción. Similar a WrapRef, podemos introducir indicaciones para el contenedor, indicaciones para la etiqueta y indicaciones para la descripción. Funciona. Pero no termina aquí. ¿Qué pasa si necesitamos agregar elementos alrededor del campo de entrada, más indicaciones? Ahora pueden ver cómo la API se vuelve abultada con este enfoque. Aquí está la API del componente de entrada de texto de Man Time. No me malinterpreten. Man Time es una gran biblioteca de componentes. Me encanta mucho.
2. Forma de configuración y composición de componentes
Eligen la forma de configuración para diseñar la API del componente. En la forma de configuración, tenemos que admitir flexibilidad con un aumento de la API y los componentes se convierten en una caja negra completa para los consumidores. Retrocedamos un paso y pensemos, ¿cómo construimos nuestra aplicación web en React? Componemos componentes. Cada componente hace su propio trabajo y eso es todo. Simple y flexible.
Eligen la forma de configuración para diseñar la API del componente. En la forma de configuración, tenemos que admitir flexibilidad con un aumento de la API y los componentes se convierten en una caja negra completa para los consumidores.
Retrocedamos un paso y pensemos, ¿cómo construimos nuestra aplicación web en React? Componemos componentes. Cada componente hace su propio trabajo y eso es todo. Simple y flexible.
Entonces, ¿qué pasa si usamos el mismo enfoque para los componentes compartibles? Obtenemos la misma claridad, simplicidad y flexibilidad. Muchas bibliotecas populares de componentes utilizan el enfoque de composición para implementar componentes accesibles como Chakra UI, Redux primitives, Hedonist UI y más.
Para admitir la accesibilidad, tenemos que usar el Contexto de React para comunicarnos entre el padre y los hijos y coordinar los atributos de accesibilidad. La configuración es fácil de usar. Realmente quieres escribir tu módulo en el estilo de la izquierda en lugar del estilo de la derecha. Pero el enfoque de composición no es la respuesta final.
3. Component Consistency and Flexibility
Tomemos Chakra UI como ejemplo. ¿Qué pasa si intercambiamos la posición de inputLeftAddon e inputRightAddon? Obtendremos inputLeftAddon mostrado en el lado derecho e inputRightAddon mostrado en el lado izquierdo. Construyendo una Lista Accesible, los padres necesitan conocer la información de los hijos para que cumpla con la accesibilidad. La forma de configuración proporciona la mejor consistencia, ya que el componente es la única fuente de verdad. La forma de composición proporciona la mejor flexibilidad, pero conduce a casos de uso no controlados y patrones incorrectos. Los slots son el estándar para los componentes web, lo que te permite definir espacios reservados que pueden ser llenados con cualquier fragmento de marcado que desees.
Tomemos Chakra UI como ejemplo. ¿Qué pasa si intercambiamos la posición de inputLeftAddon e inputRightAddon? Obtendremos inputLeftAddon mostrado en el lado derecho e inputRightAddon mostrado en el lado izquierdo. Esto rompe la semántica con un estilo roto. Tengo un pasatiempo cuando encuentro un nuevo sistema de diseño, que es probar la resonancia de los componentes. Al agregar nodos adicionales a los elementos, vemos cómo se rompen. Para un sistema de diseño dedicado, entregamos componentes bien diseñados. Pero no puedes imaginar cómo los desarrolladores alternativos los romperán de diversas formas y, desafortunadamente, no podemos evitar que lo hagan.
Dirías que no importa si seguimos el orden de diseño correctamente. Bueno, veamos otro problema más realista. Construyendo una Lista Accesible, los padres necesitan conocer la información de los hijos para que cumpla con la accesibilidad. Es fácil lograrlo utilizando la forma de configuración, ya que el padre conoce todos los elementos desde ItemPrompt. Pero es un poco engorroso para la forma de composición. El menú no puede ver sus elementos internos. El componente está ciego. Para resolver el problema, podemos usar react-children con react-clone-element. Luego podemos tomar el control de la renderización de los elementos en el componente padre. Pero en términos de extensión, volverá a romperse. Incluso si podemos iterar los hijos, el padre sigue estando ciego ya que no puede ver los detalles profundos. El problema está bien descrito por root UI, si estás interesado, aquí tengo una tarea para ti para que descubras cómo estas bibliotecas están tratando de resolver este problema.
Recapitulemos, la forma de configuración proporciona la mejor consistencia, ya que el componente es la única fuente de verdad, pero es difícil de extender y se convierte en una caja negra cuando se usan múltiples elementos. Por el contrario, la forma de composición proporciona la mejor flexibilidad, puedes personalizar tus subcomponentes libremente, pero causa otro problema, la consistencia, conduce a casos de uso no controlados y patrones incorrectos. Entonces, ¿podemos tener una forma de construir componentes con flexibilidad manteniendo la consistencia? Sí, la respuesta es no. Los slots son el estándar para los componentes web. En los componentes web, los slots se identifican por su atributo de nombre y te permiten definir espacios reservados en tus plantillas que pueden ser llenados con cualquier fragmento de marcado que desees cuando se utiliza el elemento en el marcado. Es más fácil entender el concepto en el código. Primero, definimos una plantilla con algunos espacios reservados con nombre y definimos el componente web. Luego llenamos los espacios reservados con slots con nombre. Finalmente, obtenemos el resultado con los elementos inyectados. Aquí puedes ver cómo se garantiza la consistencia. El diseño se define en la plantilla, no se describe por el orden de composición. Algunos frameworks populares como Vue y Svelte también implementaron el patrón de composición de slots a nivel de framework de manera similar.
4. React Slots RFC y los Desafíos
React no admite bien los componentes web y los slots. Se han probado varios enfoques, como usar react-children con react-clone-element, pero tienen limitaciones. React Spectrum utiliza contexto y orden de cuadrícula, pero tiene funcionalidad limitada. GitHub Primer utiliza renderizado doble, pero no admite el renderizado en el lado del servidor. Se creó un paquete experimental llamado React-core-return que parecía prometedor, pero fue eliminado. Por lo tanto, se creó React Slots RFC para abordar estos problemas.
Pero como sabes, React todavía no admite muy bien los componentes web, por no mencionar los slots. Hay muchos enfoques de la comunidad que intentan llevar los slots al mundo de React. El enfoque más común es usar react-children con react-clone-element. Pero no escala muy bien ya que el componente está ciego. React Spectrum utiliza contexto y orden de cuadrícula para resolver el problema del desorden, pero la funcionalidad es limitada. Por ejemplo, con el orden de cuadrícula, cambiamos el índice de tipo de manera inesperada. GitHub Primer utiliza el renderizado doble para abordar los slots. Renderiza los subcomponentes en un navegador con contenido vacío y recopila la información del slot primero, y luego utiliza el slot en el segundo renderizado. El problema es evidente. No admite el renderizado en el lado del servidor. Hubo un paquete experimental del equipo principal de React llamado React-core-return, que proporciona una forma de interpolar los hijos durante la fase de renderizado. Podría ser el mejor candidato para llevar los slots a React, pero desafortunadamente, fue eliminado poco después. Así que creé React Slots RFC para describir los problemas con la propuesta. Permíteme mostrarte el código directamente.
5. React Slots RFC y Composición de Componentes
Nuevamente, el componente de entrada de texto típico implementado en React Slots RFC. Introdujimos dos nuevas APIs llamadas createHost y createSlot, similares a React call return pero con un modelo mental más simple. createHost crea la plantilla y createSlot crea espacios reservados, lo que nos permite interactuar con los elementos. Esto abre nuevas posibilidades para la composición de componentes y proporciona una solución a problemas anteriores. Ahora podemos demostrar cómo React Slots RFC resuelve estos problemas y admite accesibilidad, flexibilidad y resistencia de componentes.
Nuevamente, el componente de entrada de texto típico implementado en React Slots RFC. En el RFC, introdujimos dos nuevas APIs llamadas createHost y createSlot, que son muy similares a React call return pero con un modelo mental mucho más simple. createHost crea la plantilla y createSlot crea espacios reservados, y luego se compone el Slot en el componente host. Es bastante similar a Slot para componentes web, pero mucho más poderoso, ya que no simplemente inyectamos los elementos, los interactuamos.
Abre el tercer ojo del componente, ahora el padre puede ver los detalles profundos de sus hijos, sin importar cómo se hayan extendido. Ahora, es hora de la demostración. Te mostraré cómo los problemas anteriores se resolverán perfectamente con React Slots RFC. Aquí, implementé React Slots RFC con react-core-return en 10 líneas. Y luego, el campo de texto, primero, creamos los slots para la etiqueta, el input y la etiqueta. Pasamos los hijos a createHost, luego nos da los slots. Aquí puedes ver cómo podemos interpolar los slots. Eso significa que podemos hacer todo lo que queramos en un solo lugar. Aquí puedes ver cómo admito accesibilidad para la entrada de texto. Podemos leer directamente el ID del input para la etiqueta, y también podemos iterar todo el texto.
Ahora, te mostraré algo de magia. No importa cómo componga los slots, no cambia el resultado porque el diseño está definido en la plantilla por createHost. Mientras tanto, aún somos libres de extender los slots. Por ejemplo, extendemos la etiqueta, extendemos el input, y podemos cambiar el orden de la etiqueta. Cambiará el resultado porque es una lista. Y el otro problema es la resistencia del componente. Permíteme intentar envolver la etiqueta con un nodo adicional. Funcionará porque no es un nodo. Eso es muy importante para un sistema de diseño dedicado, porque hemos preparado un muy... Hemos preparado los componentes. No deberíamos cambiar el diseño fácilmente. Volvámoslo a cambiar. Otro caso de uso es que no importa lo que usemos, no está fuera del host. Nada se renderizará. Eso también es importante para un sistema de diseño consistente. Los slots anidados también son compatibles.
6. React Slots RFC y Flexibilidad de Componentes
React Slots RFC nos permite crear campos de entrada de texto sin la necesidad de etiquetas o inputs estilizados. En su lugar, los estilizamos en la plantilla. El slot actúa como un portador de datos, lo que nos permite crear estructuras complejas como árboles sin renderizarlos en el navegador. Aún podemos acceder a los datos y beneficiarnos de las características de React, como los métodos del ciclo de vida y el contexto.
¿Has notado cómo creamos los campos de entrada de texto, no necesitamos crear una etiqueta estilizada para el campo de texto o un input para el campo de texto. En su lugar, lo estilizamos en la plantilla. El slot en sí no renderiza nada, solo actúa como un portador de datos. Eso significa mucho. Por ejemplo, podemos crear un generador de árboles. Aquí tienes un ejemplo. Creamos un árbol, pero no se renderiza en el navegador. Pero puedes verlo en la consola. Aquí está el árbol, el menú anidado, pero así es como los componemos. Pero no se renderiza en el navegador en absoluto. Aún así, podemos obtener data, porque el slot es solo un portador de data. No es necesario renderizarlo en el navegador en absoluto. Y puedes ver cómo es reactivo. O eliminar el menú anidado. Así que aún podemos beneficiarnos de todo lo que React nos proporciona. Los ciclos de vida, el estado, el contexto, y todas las demás cosas. Hay un paquete llamado React New. Intenta hacer lo mismo, pero veamos el tamaño. Es casi 100 KB minimizado, incluso 30 KB minimizado, y CCPT hace lo mismo porque tiene que incluir el reconciliador de React en él. Pero con React-SNOS podemos usar el reconciliador de React directamente. Ahora puedes ver cómo logramos flexibilidad manteniendo la consistencia en React-SNOS-RFC con un modelo de personalización descentralizada y control centralizado. ¿Qué significa eso para nosotros? Ahora, el componente host es la única fuente de verdad y toma el control de sus hijos, como en el enfoque de configuración. Así que podemos hacer que los componentes componibles sean predecibles. Puedes ver lo simple que es agregar soporte de accesibilidad. Y como no es necesario renderizar los slots en un árbol DOM, en su lugar simplemente conectamos la información y luego decidimos qué renderizar en función de la información conectada. Por lo tanto, el soporte de virtualización está listo para usar y hay más posibilidades, por ejemplo, un renderizador personalizado. Además, creé un paquete llamado create-slots que implementa completamente el RFC de react-slots, a diferencia de otros enfoques que no funcionan bien con algunas características de React, como el renderizado en el lado del servidor. Utilicé el pre-renderizado, que es similar a react-call-return, para recopilar la información de los hijos durante la fase de renderizado y utilizar la información más adelante durante la misma fase de renderizado, y luego podemos confirmar el resultado interpolado en el navegador. Y ya se ha utilizado en el sistema de diseño de Revolut. Como también estamos utilizando el enfoque de composición para construir nuestros componentes compartidos, tenemos casi 200 componentes compartidos y nos enfrentamos a más problemas de los que he discutido aquí, lo que finalmente me llevó a esta charla. Pero el paquete no es ideal, ya que repite el trabajo del reconciliador de React. Por eso todavía quiero ver el soporte de React mismo. Y eso es todo. Si quieres discutir más, puedes encontrarme en GitHub y Twitter. Gracias.