Voy a mostrar la arquitectura de React Native que utilizamos en nuestra nueva aplicación móvil en Product Hunt. Lo que aprendimos en el camino. Cómo trasladamos lo que sabemos de la web al móvil. Los temas serán el diseño de componentes reutilizables de React, GraphQL, enrutamiento en la aplicación, ciclo de vida de la aplicación, controles de teclado, mensajes de aviso y otros.
Arquitectura de React Native en Product Hunt
Video Summary and Transcription
El orador discute la arquitectura de React Native que utilizan en su nueva aplicación móvil, eligiendo React Native en lugar del desarrollo nativo debido a los recursos limitados y la necesidad de experimentar con la interfaz de usuario. La arquitectura consta de soporte, componentes y pantallas, con un enfoque en la organización del código y la extensibilidad. El orador explica el proceso de creación de pantallas, incluido el uso de React Native Navigation y un patrón de máquina de estados. También destacan el uso de GraphQL para la recuperación de datos y la navegación entre pantallas. La charla cubre la estructura de los componentes comunes, utilidades para el estilo y el soporte para el modo oscuro. En general, el orador enfatiza la importancia de aislar las dependencias y utilizar un enfoque de estructura de directorios.
1. Introducción a la Arquitectura de React Native
Hola a todos. Mi nombre es Radoslav Stankov y vengo de Bulgaria. Hoy hablaré sobre nuestra arquitectura de React Native y cómo hemos estructurado nuestra aplicación de React. Produhunt es este sitio web. Cuando decidimos reconstruir nuestra aplicación móvil, teníamos dos opciones: usar Swift nativo o usar React Native. Tuvimos una experiencia terrible con React Native en el pasado, pero solo teníamos dos desarrolladores con experiencia en Swift y ninguno en Android. También necesitábamos experimentar con la interfaz de usuario y admitir Android, así que elegimos React.
Hoy hablaré un poco sobre nuestra arquitectura de React Native y cómo hemos estructurado nuestra aplicación de React. Pero antes de entrar en los detalles técnicos, para una buena arquitectura, es necesario conocer el contexto, como de dónde viene el equipo, cuáles son los requisitos y cómo se construye todo.
Produhunt es este sitio web. Así es como nos vemos en la web. Y he dado presentaciones anteriores sobre la arquitectura de Produhunt. No entraré en detalles allí. Pero cuando decidimos reconstruir nuestra aplicación móvil a principios de este año, en realidad la lanzamos este año, comenzamos un poco más tarde que eso. Nos preguntábamos, bueno, ahora deberíamos hacer un gran reinicio en nuestra aplicación móvil. Y para nuestro caso, básicamente teníamos dos opciones.
Una era usar Swift nativo y la otra era usar React Native. Realmente tuvimos una experiencia terrible hace más de dos años cuando inicialmente comenzamos con React Native. Fue bastante desafiante. Por otro lado, solo teníamos dos desarrolladores que tenían algo de experiencia en Swift, no muy nuevos, y no teníamos ningún desarrollador con experiencia en Android. Y nuestra aplicación realmente necesitaba funcionar en Android también. Además, Apollo, así que usamos GraphQL para todas nuestras transferencias de datos y la mejor manera de usarlo es Apollo, en mi opinión. Apollo para Swift no es tan bueno como Apollo para JavaScript. Y nuestro equipo ya sabía cómo lidiar con React y Apollo. Lo otro que necesitábamos para esta aplicación era hacer muchas experimentaciones de interfaz de usuario, y nuevamente, también necesitábamos Android.
Entonces, a principios de 2020, estábamos haciendo experimentos de aplicaciones móviles para YourStack. A principios de 2000, trabajamos en dos aplicaciones para algunos de nuestros productos y experimentación. Una era YourStack, la otra se llamaba StackCamp. Ambas no se lanzaron porque eran solo internas. Probamos algunas ideas allí. Así que, sí, no se lo digas a nadie. A partir de eso, pudimos comparar los ecosistemas de React y Swift. Y decidimos ir con React simplemente porque necesitábamos Android.
2. Visión general de la arquitectura de React Native
Nuestro equipo ya conocía React Native y decidió utilizarlo junto con GraphQL y Apollo. Priorizamos facilitar las operaciones comunes, organizar el código, aislar las dependencias y garantizar la extensibilidad y usabilidad. Nuestra arquitectura consta de tres pilares: soporte, componentes y pantallas. La estructura de directorios de nuestra aplicación móvil refleja esta arquitectura. Comenzamos con la capa superior, las pantallas, que se definen en la app GS y utilizan React Native Navigation.
En cuanto a la arquitectura, si no sabes por dónde empezar, es muy fácil encontrarse en una situación en la que te quedas atascado. Así que queremos avanzar rápido y desayunar, no romper cosas rápidamente. Queremos avanzar. Por tanto, en nuestra arquitectura tenemos cuatro objetivos. El primero es facilitar las operaciones comunes para que los ingenieros puedan añadir fácilmente nuevas funcionalidades. Luego, necesitamos organizar el código para que los nuevos miembros del equipo puedan incorporarse fácilmente al código base. Aislar las dependencias, como en el mundo JavaScript y en prácticamente cualquier mundo moderno en el que trabajemos con dependencias externas que pueden desincronizarse fácilmente, quedar obsoletas y romper muchas cosas sin nuestro control. Y por último, la extensibilidad y usabilidad. Queremos construir cosas que podamos ampliar y reutilizar a lo largo del tiempo. Pero lo más importante para nosotros es facilitar las operaciones comunes para que todo lo que un desarrollador tenga que hacer a diario sea muy fácil para ellos.
La forma en que veo las partes de la arquitectura es a través de tres pilares: el soporte, los componentes y las pantallas. Así es como lo veo en mi cabeza. Tenemos una gran área de soporte que nos ayuda a construir los componentes reutilizables principales y luego construimos las pantallas sobre eso. Para alguien más familiarizado, así es como puedes imaginarlo. Tienes los pilotos, los colocas en las líneas y forman a Voltron y lo hacen realmente bien. Si miras nuestro directorio, así es como se ve la estructura de directorios de la aplicación móvil y representa la arquitectura de esta aplicación. Y si te fijas, si lo marco con colores aquí, tenemos soporte, componentes y pantallas.
Entonces, una vez más, hablemos de código. Empecemos por la capa superior porque en realidad nos ayuda a entender las capas de ambas si empezamos al revés. Desde las pantallas. Todas las pantallas se definen en la app GS. Y aquí, lo que hacemos es utilizar la navegación de React Native
3. Creación y estructura de pantallas en React Native
Experimentamos con diferentes opciones de navegación, pero React Native Navigation se ajustó mejor a nuestro estilo de trabajo. Nuestra pantalla principal consiste en una pila principal que adjunta las pantallas. Las pantallas están organizadas en una carpeta de pantallas, y cada pantalla se exporta desde un archivo índice. Cada pantalla tiene su propio directorio, que incluye componentes privados, una consulta de gráficos para la recuperación de datos, utilidades y el archivo JS de índice. Las pantallas siguen un patrón de máquina de estados, comenzando con un diseño, pasando a un estado de carga y manejando los estados de error y carga. Hemos extraído una utilidad llamada CreateScreen para simplificar la creación de pantallas mediante la definición del nombre de la pantalla, la consulta, las variables de consulta, el componente y el tipo.
Experimentamos con otras opciones de navegación, pero esta fue la que mejor se adaptó a nuestro estilo de trabajo. Es una biblioteca realmente buena y casi no tuvimos problemas con ella.
Así es como se ve nuestra pantalla principal, como una versión resumida. Tenemos esta pila principal a la que adjuntamos las pantallas. Observa que aquí simplemente estamos haciendo una expansión de las pantallas. Las pantallas provienen de esta carpeta de pantallas y están en el archivo índice, donde simplemente exportamos todas las pantallas diferentes. Tenemos un lugar central donde un desarrollador puede rastrear dónde se encuentra su pantalla. Es como un mapa y para las pantallas, así es como se ve una pantalla.
Básicamente tenemos una lista plana donde, en la raíz, tenemos el nombre de la pantalla. Luego tenemos un directorio donde tenemos todo lo relacionado con la pantalla. Por ejemplo, en esta pantalla, tenemos dos componentes privados, que son la búsqueda y el elemento ajustado. Estos son componentes que solo se utilizan en la pantalla. Tenemos la consulta de gráficos, la consulta que obtendrá los datos para la pantalla. Tenemos algunas utilidades y el archivo JS de índice es la pantalla.
Si pensamos en la pantalla, en realidad es una máquina de estados y esta máquina de estados comienza con un diseño. Luego pasa al estado de carga dentro de este diseño. Si algo malo sucede, hay un estado de error y este error puede ser un error del servidor, error de no encontrado, autorización, autenticación y errores similares. Por otro lado, podemos tener el estado de carga. Cuando la pantalla se carga, hay un par de operaciones que siempre debemos hacer. Necesitamos renderizar la pantalla. Y nos dimos cuenta de que tenemos mucho código de estado en nuestro sistema.
Entonces, lo que hicimos fue extraer una utilidad que tenemos en la web llamada CreateScreen. Y esta CreateScreen nos permite definir, okay, ¿cuál es el nombre de la pantalla? Es muy útil para depurar. ¿Cuál es la consulta, que se utilizará para obtener los datos? ¿Cuáles son las variables de consulta para esta consulta? ¿Qué componente va a renderizar esta pantalla y simplemente pasamos con este componente los datos y los parámetros? ¿Qué tipo de pantalla es esta? En nuestra aplicación tenemos tres tipos de pantalla. Tenemos una pantalla que es como una pantalla de empuje, que simplemente la empujamos hacia atrás. Tenemos una hoja de acciones, que se abre como una hoja de acciones. Y tenemos una superposición, que es una superposición. También hay un par de otras opciones como el fondo de pantalla.
4. Creación de pantallas y navegación en React Native
Las diferentes pantallas en nuestro sistema tienen diferentes fondos y títulos en la navegación. Manejamos las diferentes pantallas utilizando un comando para crear pantallas. Nuestra arquitectura de la aplicación involucra pantallas, consultas, componentes y hooks. GraphQL conecta todo en nuestra aplicación, proporcionando seguridad de tipos y la capacidad de anidar fragmentos. También utilizamos un truco llamado rutas, que permite una navegación fácil entre pantallas.
Las diferentes pantallas en nuestro sistema tienen diferentes fondos y títulos en la navegación. También hay un área segura, que es el estilo del iPhone X sin botón de inicio. ¿Y cómo lo hacemos en diferentes pantallas? Así que cada desarrollador puede construir una pantalla simplemente llamando a este comando.
Lo que tenemos es la pantalla, que es create screen, pasa por el índice de la pantalla y luego pasa por la aplicación. Aquí hay un ejemplo de pantalla. Esta es la pantalla de inicio que tenemos. Solo tenemos una consulta con su variable. Y luego tenemos el componente y usamos dos hooks para configurar la navegación en vivo. Configuramos las notificaciones push y renderizamos un desplazamiento infinito de elementos. Y nuevamente, GraphQL es lo que también conecta todo en nuestra aplicación. En el backend, usamos Rails y exportamos GraphQL. GraphQL va a Apollo y Apple nos ayuda a generar TypeScript. Usando el generador de código de Apollo, podemos tomar los fragmentos de GraphQL y convertirlos en tipos y TypeScript. Y podemos usarlos en nuestras páginas. Esto nos brinda seguridad de tipos. Y lo bueno de esto es que podemos anidarlos. Podemos adjuntar fragmentos con más fragmentos. Y así es como comenzamos. La forma en que comenzamos es desde la consulta de la página y luego obtiene los fragmentos y desciende al nivel del componente. Y para mí eso sigue siendo mágico. Es muy mágico ir al backend, probar el cambio y la aplicación móvil se queja de que este campo ya no existe o que es nulo, pero esperabas que no fuera nulo. Y esto conecta toda la aplicación.
Otro truco genial que usamos se llama rutas. No he visto esto en aplicaciones React Native, así que creo que puede ser muy útil para todos. Lo que tenemos es este archivo donde simplemente exploramos esta lista de rutas, donde básicamente decimos que nuestras pantallas son rutas. Por ejemplo, tenemos una ruta de inicio de sesión de perfil porque en nuestra aplicación, en cada pantalla, puedes ir a casi cualquier otra pantalla. Simplemente puedes decir, okay, esta es la descripción de dónde iría esta cosa. Y tenemos esos hooks para navegar entre las diferentes pantallas. Y puedes decir, okay, tengo una ruta, volver, pantalla de inicio de sesión, pantalla de perfil. Y puedo usar esto en un botón.
5. Estructura de botones y componentes en React Native
Tengo un botón que navega a un producto con ID5. Tenemos un componente de botón principal con funcionalidad incorporada. Nuestros componentes se utilizan en múltiples pantallas y se organizan en una estructura de directorios. Tenemos componentes comunes, como texto y botones, con funcionalidad incorporada. También tenemos componentes de diseño, que incluyen filas flexibles, columnas flexibles y ayudantes como expandir flex y cuadrícula. Tenemos componentes de utilidad, estilo y dominio, con anidamiento profundo y un enfoque en el diseño atómico. Por último, tenemos funciones de utilidad para diversos propósitos.
Puedo decir que tengo un botón y navega al producto con ID5. Y esto es algo muy común. Entonces, lo que hacemos es que lo tenemos incorporado en nuestro componente de botón principal. Y nuestro componente de botón principal simplemente tiene esto.
Bueno, el siguiente paso del que voy a hablar son los componentes. Básicamente, son cosas que se utilizan en más de dos pantallas. Y más de una pantalla, en realidad. Así que usamos el mismo componente como directorio donde la carpeta raíz muestra todo lo público. Las cosas privadas están todas anidadas. Algunos componentes incluso tienen, como, 45 niveles de componentes. Mantenemos aquí el graphQL, la mutación, el índice es donde exportamos. Y tenemos componentes bastante comunes. Tenemos nuestra propia versión de texto, para tener un tamaño de texto consistente. Tenemos esta estructura de espaciado. Otra cosa que tenemos son, nuevamente, los botones habituales. Y para nosotros, los botones no solo son estilo, sino que también tienen funcionalidad. Podemos decir, ok, el botón puede enlazar a una página. Si la función, que se hace clic o se presiona, devuelve una promesa, el botón no te permite presionarlo nuevamente. También tenemos confirmación incorporada, requiere inicio de sesión y también está integrado con GraphQL. Entonces, el botón puede ejecutar una mutación y manejar los resultados de la mutación. También tenemos paquetes para botones. Tenemos un botón que es solo texto, un botón que es un icono, uno sólido y uno de contorno.
Para los diseños, hemos extraído el sistema de flex incorporado en Apollo en React Native porque básicamente nos cansamos de escribir el mismo estilo una y otra vez. Tenemos esta fila flexible y columna flexible y un par de ayudantes como expandir flex, que expande el flex a toda la pantalla, una cuadrícula, que son esos puntos, y un texto, que es básicamente un truco para que los textos floten correctamente con React Native. Y como mencioné, tenemos componentes de utilidad, estilo y dominio, así que también tenemos componentes de dominio. Como esta es la página de inicio y si vemos el elemento de publicación, tiene un botón en negrita. Y este botón en negrita utiliza el componente de botón, pero también el elemento de publicación utiliza el botón en negrita y el botón, por lo que tenemos este anidamiento profundo de componentes de utilidad y componentes de dominio. Es como un diseño atómico, simplemente decimos, ok, tenemos los componentes genéricos y luego tenemos los componentes de dominio, que están relacionados con una entidad de dominio. Por lo general, la diferencia es que uno tiene fragmentos y el otro no. Y la última cosa que quería compartir son las utilidades.
6. Utilidades y Estilos en React Native
Quiero hablar sobre algunas de las utilidades que tenemos en nuestro sistema, centrándonos en el estilo. Agregamos un ayudante para márgenes y rellenos, que resultó ser muy útil. Otra característica que implementamos fue el soporte para el modo oscuro utilizando React Native Dynamic. Tenemos soporte incorporado para el modo oscuro y un conjunto principal de componentes, así como componentes de dominio que utilizan GraphQL. En general, hemos aislado nuestras dependencias y utilizado un enfoque de construcción de directorios.
Quiero hablar sobre algunas de las utilidades que tenemos en nuestro sistema, y simplemente viven en utilidades, en hooks y en estilos. Lo dividimos en tres carpetas, porque para los ingenieros descubrimos que es bueno tener un lugar dedicado para cosas como hooks, y sabemos que los archivos allí son muy especiales. Quiero centrarme en el estilo.
Un problema para el cual estábamos escribiendo mucho estilo era para márgenes y rellenos. Entonces, lo que hicimos fue agregar este ayudante que usamos en nuestro componente principal, que tiene esas propiedades para márgenes y rellenos. Aquí hay algo que en la web usamos la propiedad flex-gap, pero esto no es algo que podamos implementar con React Native. Y debido a esto, simplemente tenemos este ayudante de estilo donde tenemos margen, fondo, relleno y todas las asociaciones. Y cada componente puede tener esta propiedad de espaciado, por lo que podemos agregar cualquier espaciado a esos componentes, y esto fue muy útil.
La otra cosa que formaba parte del nuevo conjunto de funciones era tener soporte para el modo oscuro, que es una característica genial. Entonces, usamos este paquete llamado React Native Dynamic, y creamos un envoltorio alrededor de él donde puedes usar esta hoja de estilo dinámica y, según el color, decides qué color estás usando. Y tenemos un par de ayudantes y temas de estilo, como por ejemplo, usar color de fondo. Y tenemos este mapa de colores, que es el valor dinámico. Entonces, en el texto con este color, el texto secundario es con este color, y cada color en nuestra aplicación está definido a este color y debe ir a React Native Dynamic. Así que en realidad tenemos soporte incorporado para el modo oscuro. Y básicamente así es como se combina toda la aplicación. Como construimos sobre los cimientos, que son las utilidades. Y tenemos muchas más utilidades inteligentes. Son mucho más ayudantes internos. Pero los que te mostré, creo que son los más memorables y útiles, y no he visto que se utilicen mucho en otras aplicaciones hasta ahora. Y luego, sobre ellos, construimos este conjunto principal de componentes, que es nuestro conjunto principal, que son cosas como botones, texto, desplazamiento infinito, cosas en las que no les importa la lógica de dominio. Y luego tenemos el componente de dominio, que es como botón de publicación, botón de publicación, elemento de publicación, miniatura de publicación, que básicamente son cosas con fragmentos, cosas que toman cosas de GraphQL. Y luego, todo esto se combina en las páginas, que están envueltas con esta ayuda o crean página. Entonces, para resumir eso, si quieres sacar algunas conclusiones de mi charla, sí, GraphQL es increíble. Ayuda mucho a diseñar un sistema que utiliza GraphQL en todas partes. La creación de pantallas es algo que me sorprende no haber visto a muchas personas usar un patrón como ese o el escalador de pantallas. Y en cuanto a los componentes, fíjate cómo hemos aislado muchas de nuestras dependencias. Utilizamos un enfoque de construcción de directorios y tenemos este concepto de componente de dominio. Así que, sí, muchas gracias. Eso es todo de mi parte. Gracias.