Muy buenas, me llamo Luis y esta vez les traigo otro nuevo post.
Índice
Cuestión de gustos, claro, pero aquí hay un enfoque que escala
¿Existe un método perfecto para estructurar un proyecto React? La documentación oficial no te ayudará mucho. React es una biblioteca y no un marco, por lo que no tiene opiniones sobre cómo debes construir tu aplicación.
Esto es bueno porque nos da la libertad de estructurar nuestra aplicación como queramos.
Los desarrolladores experimentados no encontrarán una desventaja en esto, pero los desarrolladores jóvenes pueden tener dificultades para encontrar una manera de construir una estructura ordenada.
Mi filosofía
Cada proyecto responde a una necesidad específica y, por lo tanto, necesita una estructura y reglas específicas. Hay tantas formas malas como buenas de estructurar su aplicación.
El enfoque que describiré en este artículo podría no ser la respuesta perfecta a las necesidades específicas de su proyecto.
Dicho esto, he estado trabajando con React durante un tiempo en proyectos de diferentes tamaños. La estructura que estoy usando hoy es realmente diferente a la que usaba hace unos años.
De hecho, he estado mejorando mi forma de estructurar una aplicación para cada proyecto que he creado. Como estoy bastante contento con la estructura que estoy usando estos días, decidí compartirla.
A menos que esté completamente seguro de hacia dónde se dirige, no diría que necesita aplicar una estructura estricta a su aplicación desde el primer día.
El código siempre se puede refactorizar más adelante sobre la marcha. Eso es cuestión de elección.
Por lo tanto, trato de aplicar lo antes posible mis necesidades de estructura, ya que lo considero bueno para mi productividad (lo que no evita una posible refactorización en el futuro, obviamente).
¡Entremos en ello!
La estructura global
¡Bien! El directorio raíz es el mismo para todos los proyectos y no hay mucho debate al respecto, ya que solo se trata de cuestiones de configuración. ¡Así que iré directamente a la carpeta ./src
, donde la gente tiene opiniones y gustos!
Así es como estructuro mi carpeta ./src
:
/src │ ├── assets │ └── ... │ ├── components │ └── ... │ ├── containers │ └── ... │ ├── core │ └── ... │ ├── app.tsx ├── index.tsx └── router.tsx
Entonces, ¿qué está pasando aquí?
Sí, me encanta TypeScript
y lo tratarás en este artículo. TSX
es la extensión de archivo para archivos TypeScript
que contienen código JSX
.
-
./index.tsx
: El punto de entrada del proyecto. Aquí es donde inicializo las bibliotecas que uso, como el proveedor de temas (de los componentes con estilo ), la tienda ( Redux , Apollo o su biblioteca de elección), el enrutador ( React Router ) y también incluyo mi componente<App />
, obviamente. -
./app.tsx
: Contiene el componente<Router />
. Aquí también es donde implemento mis funciones (principalmente contenedores, pero lo aprenderemos más adelante) que se utilizan en toda la aplicación, como el sistema modal, el contenedor de notificaciones, los trabajadores del servicio, etc. -
./router.tsx
: Compuesto por un<Switch />
, un<Route />
y los principales componentes de mi proyecto. Si necesito subrutas, las manejan los contenedores que las utilizan.
No es una gran sorpresa. Esta carpeta obviamente contiene imágenes, íconos, fuentes y más.
Pero como no quiero que se convierta en un enorme cubo de basura donde el equipo simplemente tira cosas al azar a medida que la aplicación crece, los activos se ordenan por contexto.
/assets │ ├── auth // ◀️ Assets specific to the auth container │ ├── connected.mp3 │ └── background.png │ ├── fonts │ └── ... │ ├── ionicon // ◀️ An icon Library │ └── ... │ ├── profile // ◀️ Assets specific to the profile container │ └── background.png │ ├── logo.svg └── ...
Por ejemplo, si mi aplicación usa un logotipo, probablemente se pueda usar en cualquier parte de mi proyecto. Por este motivo, éste pertenece a la raíz de la carpeta ./assets
.
Pero una imagen de fondo que solo se usa en el contenedor Auth
pertenece a la subcarpeta ./auth
.
Me resulta extremadamente sencillo navegar por mis activos de esta manera.
Este método también facilita la búsqueda de activos que ya no se utilizan. En primer lugar, no debería ser un problema, pero seamos honestos: a menudo nos olvidamos de limpiar esta carpeta.
¿Cómo debería agrupar sus componentes? Muchos gustos diferentes aquí. La documentación de React sugiere dos soluciones:
- Agrupar archivos por características o rutas.
- O agrupar archivos por tipo (CSS, componentes, pruebas, etc.).
Si estuviera trabajando en un proyecto con alrededor de 100
componentes, la segunda solución me volvería loco.
Prefiero usar la primera opción, o al menos una versión mejorada.
/src │ ├── components │ └── ... │ ├── containers │ └── ... │ └── ... // ◀️ other folders we saw above
Primero abordemos la carpeta de componentes.
Para que un componente de React se gane un lugar en la carpeta ./components
, debe obedecer dos reglas:
- Debe ser un componente de presentación, lo que significa que no está conectado al estado de la aplicación y ciertamente no obtiene ni publica datos.
- Puede interactuar con el contenedor principal (activando una función que se pasó en Props, por ejemplo). Estoy bien con eso, pero eso es todo.
- Debe utilizarse en varios componentes o contenedores.
Los componentes hacen que las cosas se vean bien, mientras que los contenedores hacen que las cosas funcionen.
Dependiendo de lo que logre su aplicación, puede ser una página o un módulo de una página. Me gusta llamarlas funciones. El encabezado es una característica.
La página de autenticación también es una característica. Logran algo específico.
Los contenedores tienen estado, lo que significa que pueden:
- Suscríbete a la tienda.
- Desencadenar efectos secundarios (interactuar con la tienda, buscar o publicar datos, etc.).
- Manejar el envío de eventos analíticos.
- Proporcione estado, datos y acciones a los componentes secundarios a través de accesorios.
Esto deja claro en mi estructura de archivos dónde está la lógica y dónde está la parte de presentación.
Mi carpeta principal también podría llamarse Commons o Shared. Contiene todo lo que se usa en la aplicación.
/core │ ├── models │ ├── notification.model.ts │ ├── user.model.ts │ └── ... │ ├── services │ ├── notification.ts │ ├── notification.test.ts │ ├── user.ts │ ├── user.test.ts │ └── ... │ ├── store │ ├── middlewares │ │ └── ... │ │ │ ├── auth │ │ ├── actions.ts │ │ ├── epics │ │ │ ├── some-side-effect.ts │ │ │ ├── fetch-stuff.ts │ │ │ └── ... │ │ │ │ │ ├── reducer.ts │ │ └── selectors.ts │ │ │ ├── index.ts │ └── state.ts │ └── theme ├── animations.ts ├── global-state.ts └── index.ts
Dado que uso TypeScript
para mis proyectos, significa que necesito un lugar para almacenar todos mis tipos e interfaces.
Hay un archivo para cada tipo de datos ( User
, Product
, Notification
, etc.).
El trabajo de un contenedor es habilitar la función. Para mantener mis contenedores lo más ajustados posible, los servicios manejan la lógica empresarial que haría mucho ruido innecesario en el componente.
La carpeta de la tienda contiene la configuración de la tienda y sus middlewares, nada sofisticado. También contiene todos los reductores, sus acciones, sus epopeyas y sus selectores.
Dónde debemos colocar los reductores es a menudo un debate. ¿Deberíamos ponerlos al lado del contenedor donde se usan principalmente o en un directorio separado con todos los demás reductores?
Ambas opciones funcionan. Prefiero agruparlos en la carpeta ./core/store
por el simple hecho de que el estado se usa a menudo en varios componentes, al igual que las acciones.
Estoy usando Redux-Observable
para lidiar con los efectos secundarios de mi aplicación. Ese es el equivalente a la forma NgRx
de manejar los efectos secundarios para Angular, y lo encuentro muy conveniente.
Por eso ves una carpeta épica. Dado que pueden ser activados por varios contenedores, los coloco en su carpeta respectiva en la tienda.
./theme
. Como muchos de nosotros, utilizo la popular biblioteca de componentes con estilo para diseñar mis componentes.
La carpeta de temas es donde pongo los estilos globales de mi proyecto, así como mis animaciones CSS.
También almaceno las variables del tema de mi proyecto en el archivo index.ts
, como se describe en la documentación.
La estructura de una carpeta de componentes
Los contenedores son responsables de más cosas que componentes de presentación, pero los estructura de la misma manera. Por lo tanto, el contenido a continuación es válido para contenedores y componentes.
Así es como puede estructurar la carpeta de un componente:
/my-component │ ├── components // ◀️ Only for a Container! │ ├── sub-componentA │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── styled.ts │ │ │ └── sub-componentB │ └── ... │ ├── service // ◀️ Optional, mostly for Containers │ ├── index.test.tsx │ └── index.ts │ ├── index.test.tsx ├── index.tsx └── styled.ts
-
./index.tsx
: Donde se define mi componente. Algunas personas prefieren nombrarlo después del nombre del componente que exporta. Eso es cuestión de gustos. Ambos métodos tienen sus pros y sus contras. No me importa cuál se use en mis proyectos. -
./styled.ts
: Donde reside el diseño de mi componente. Todos los componentes creados en este archivo siguen la misma convención. Se llamanStyledXxx
y se utilizan de la siguiente manera:<StyledXxx />
. Como un componente puede usar muchos componentes secundarios, esta es una forma fácil de detectar si un componente solo es responsable del diseño o no. -
./index.test.tsx
: Donde viven las pruebas.
Un contenedor a menudo usa sus propios componentes de presentación. Por "propio"
, me refiero a que esos componentes de presentación solo se utilizan dentro de este contenedor.
Como quiero mantener el código cerca de donde se usa, tiene sentido dejar que esos componentes vivan en el contenedor.
La subcarpeta ./components
está tapada a una profundidad de 2
, lo que deja el contenedor fácil de navegar. Una profundidad más profunda aumentaría innecesariamente la complejidad.
En el ejemplo anterior, <SubComponentB />
podría ser un hijo de <SubComponentA />
, pero eso no importa. Viven al mismo nivel.
El servicio me ayuda a subcontratar la lógica comercial pesada específica de mi contenedor. No todos los contenedores necesitan su propio servicio, ya que es posible que la lógica "común"
ya exista en la carpeta principal, pero podría ser necesaria en algún momento.
Como quiero mantener mi contenedor lo más delgado posible para que pueda entenderse a primera vista, coloco esta lógica en un archivo de servicio junto a mi componente. Este método también hace que las pruebas sean más limpias.
Algunos podrían decir que en realidad dificulta la comprensión de lo que está sucediendo, ya que necesitan abrir el servicio para verificar la lógica.
No veo por qué esto es un problema. Además, dado que uso TypeScript
, pasar el cursor por el método me dice lo que obtengo.
Estructura del componente React
Tener una buena estructura de archivos es crucial, pero también centrémonos en el componente React en sí, ya que es allí donde pasamos la mayor parte de nuestro tiempo.
Aquí hay algunas reglas y cómo estructuro mis componentes de React:
<Perfil />
.El nombre debe ser único y claro sobre lo que logra ese componente.
¿Por qué es importante nombrar sus componentes? Me gusta llamar a mis colegas por sus nombres; lo mismo ocurre con mis componentes.
Más en serio, ayuda con la depuración. Esto es lo que sucede con un componente anónimo:
export default (props) => { return <p>Don't do this</p>; }
Si inspecciona su aplicación con la herramienta de desarrollo React
, todo aparece como Anonymous
, lo cual es realmente frustrante. Lo mismo ocurre con los errores de tiempo de ejecución …
React Dev Tools
.¡Nombrar sus componentes le facilita la vida!
const Better = (props) => { return <p>Do This instead</p> } export default Better;
React Dev Tools
.En un gran proyecto, desea que sus componentes tengan un nombre.
Cuando abro un componente de React, quiero ver lo que recibe a primera vista. Es por eso que coloco la interfaz de Props en la parte superior del archivo, justo debajo de las importaciones.
Luego desestructuro los accesorios en el parámetro de la función. Dado que VSCode
solo resalta los accesorios que se utilizan, hace que sea más sencillo ver si un accesorio todavía se usa o no.
Se trata de mantener mi componente lo más limpio posible.
Son ruidosos, pero los necesitamos. Están en la parte superior del componente para que no me molesten más adelante cuando lea el resto del archivo.
Estas reglas son simples, fáciles de seguir y no representan mucho trabajo, pero aun así te hacen la vida un poco más fácil.
Tenga en cuenta que el ejemplo anterior es muy simple. Los componentes suelen ser más grandes y menos fáciles de leer que este.
Ahí es donde se detienen mis ojos, justo después de la interfaz de Props. Están justo debajo de los ganchos, ya que representan la información más valiosa: qué datos recibe mi contenedor de la tienda y qué estado administra localmente.
Sé de qué está hecho mi componente. Los efectos me dicen cómo se comporta. En orden de importancia, eso es lo que quiero ver debajo del estado del componente.
Por último, la lógica empresarial. Tengo toda la información que necesito para comprender la lógica.
Conclusión
A continuación se muestra la estructura completa que analicé en este artículo.
Este es mi enfoque para estructurar una aplicación React. Es un enfoque que escala, funciona muy bien y me permite trabajar rápido sin comprometer la calidad del código base a medida que crece la aplicación.
Hay muchas otras formas de estructurar su aplicación, y yo diría que más que las elecciones de estructura que haga, la consideración más importante es mantener la coherencia en todo el proyecto.
/src │ ├── assets │ ├── auth │ │ │ │ │ ├── connected.mp3 │ │ └── background.png │ │ │ ├── fonts │ │ └── ... │ │ │ ├── ionicon │ │ └── ... │ │ │ ├── profile │ │ └── background.png │ │ │ ├── logo.svg │ └── ... │ ├── components │ ├── componentA │ │ ├── service │ │ │ ├── index.test.tsx │ │ │ └── index.ts │ │ │ │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── styled.ts │ └── ... │ ├── containers │ ├── containerA │ │ ├── components │ │ │ ├── sub-componentA │ │ │ │ ├── index.test.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── styled.ts │ │ │ │ │ │ │ └── sub-componentB │ │ │ └── ... │ │ │ │ │ ├── service │ │ │ ├── index.test.tsx │ │ │ └── index.ts │ │ │ │ │ ├── index.test.tsx │ │ ├── index.tsx │ │ └── styled.ts │ │ │ └── ... │ ├── core │ ├── models │ │ ├── notification.model.ts │ │ ├── user.model.ts │ │ └── ... │ │ │ ├── services │ │ ├── notification.ts │ │ ├── notification.test.ts │ │ ├── user.ts │ │ ├── user.test.ts │ │ └── ... │ │ │ ├── store │ │ ├── middlewares │ │ │ └── ... │ │ │ │ │ ├── auth │ │ │ ├── actions.ts │ │ │ ├── epics │ │ │ │ ├── some-side-effect.ts │ │ │ │ ├── fetch-stuff.ts │ │ │ │ └── ... │ │ │ │ │ │ │ ├── reducer.ts │ │ │ └── selectors.ts │ │ │ │ │ ├── index.ts │ │ └── state.ts │ │ │ └── theme │ ├── animations.ts │ ├── global-state.ts │ └── index.ts │ ├── app.tsx ├── index.tsx └── router.tsx
Espero que te sea de utilidad. Gracias por leer este post.
Añadir comentario