Estructurar React Aplicaciones

El material, cuya traducción publicamos hoy, revela los enfoques utilizados por su autor al estructurar las aplicaciones React. En particular, discutiremos aquí la estructura de carpetas utilizada, el nombre de las entidades, los lugares donde se encuentran los archivos de prueba y otras cosas similares.

Una de las características más agradables de React es que esta biblioteca no obliga al desarrollador a acatar estrictamente ciertas convenciones con respecto a la estructura del proyecto. Gran parte de esto queda a discreción del programador. Este enfoque es diferente del que, por ejemplo, se adoptó en los marcos Ember.js o Angular. Proporcionan a los desarrolladores más funciones estándar. Estos marcos proporcionan convenciones con respecto a la estructura de proyectos y reglas para nombrar archivos y componentes.



Personalmente, me gusta el enfoque adoptado por React. El hecho es que prefiero controlar algo yo mismo, sin depender de ciertos "acuerdos". Sin embargo, el enfoque para estructurar proyectos que ofrece Angular ofrece muchas ventajas. La elección entre libertad y reglas más o menos rígidas se reduce a lo que está más cerca de usted y su equipo.

A lo largo de los años de trabajo con React, he intentado muchas formas diferentes de estructurar aplicaciones. Algunas de las ideas que apliqué resultaron ser más exitosas que otras. Por lo tanto, aquí voy a hablar sobre todo lo que se ha demostrado bien en la práctica. Espero que encuentres algo aquí que te sea útil.

No estoy tratando de mostrar aquí una forma "correcta" de estructurar aplicaciones. Puede tomar algunas de mis ideas y cambiarlas para adaptarlas a sus necesidades. Es muy posible que no estés de acuerdo conmigo si continúas trabajando como antes. Diferentes equipos crean diferentes aplicaciones y utilizan diferentes medios para lograr sus objetivos.

Es importante tener en cuenta que si mira el sitio web de Thread , en el que participo en el desarrollo, y mira el dispositivo de su interfaz, encontrará lugares en los que no se respetan esas reglas de las que hablaré. El hecho es que cualquier "regla" en la programación debe tomarse solo como recomendaciones, y no como estándares integrales que sean válidos en cualquier situación. Y si cree que algún tipo de "reglas" no le conviene, usted, en aras de mejorar la calidad de lo que está trabajando, debe encontrar la fuerza para desviarse de estas "reglas".

En realidad, ahora, sin más preámbulos, les ofrezco mi historia sobre la estructuración de aplicaciones React.

No te preocupes demasiado por las reglas.


Quizás decida que la recomendación de que no se preocupe demasiado por las reglas parece extraña al comienzo de nuestra conversación. Pero esto es exactamente lo que quiero decir cuando digo que el principal error que los programadores tienen en términos de observar las reglas es que los programadores le dan demasiada importancia a las reglas. Esto es especialmente cierto al comienzo del trabajo en un nuevo proyecto. En el momento de la creación del primer index.jsx simplemente imposible saber qué es lo mejor para este proyecto. A medida que se desarrolle el proyecto, naturalmente llegará a algún tipo de estructura de archivos y carpetas, que probablemente sea bastante bueno para este proyecto. Si durante la continuación del trabajo resulta que la estructura existente no es exitosa, se puede mejorar.

Si lees esto y te sorprendes pensando que no hay nada en tu aplicación que se esté discutiendo, entonces esto no es un problema. Cada aplicación es única, no hay dos equipos de desarrollo absolutamente idénticos. Por lo tanto, cada equipo, trabajando en un proyecto, llega a algunos acuerdos con respecto a su estructura y métodos de trabajo en él. Esto ayuda a los miembros del equipo a trabajar productivamente. No se esfuerce, habiendo aprendido cómo alguien está haciendo algo, preséntese esto de inmediato. No intente introducir en su trabajo lo que se llama en ciertos materiales, e incluso en esto, la "forma más efectiva" para resolver un problema. Siempre me he adherido y adhiero a la siguiente estrategia con respecto a tales recomendaciones. Tengo mi propio conjunto de reglas, pero al leer sobre cómo actúan los demás en ciertas situaciones, elijo lo que me parece exitoso y adecuado para mí. Esto lleva al hecho de que con el tiempo, mis métodos de trabajo mejoran. Al mismo tiempo, no tengo ningún shock y no deseo volver a escribir todo desde cero.

Los componentes importantes se encuentran en carpetas separadas


El enfoque para colocar archivos de componentes en carpetas a los que llegué es que aquellos componentes que pueden considerarse "importantes", "básicos", "básicos" en el contexto de la aplicación se colocan en carpetas separadas. Estas carpetas, a su vez, se encuentran en la carpeta de components . Por ejemplo, si estamos hablando de una aplicación para una tienda electrónica, entonces el componente <Product> utilizado para describir el producto puede reconocerse como un componente similar. Esto es lo que quiero decir:

 - src/  - components/    - product/      - product.jsx      - product-price.jsx    - navigation/      - navigation.jsx    - checkout-flow/      - checkout-flow.jsx 

En este caso, los componentes "secundarios" que solo utilizan ciertos componentes "principales" se encuentran en la misma carpeta que estos componentes "principales". Este enfoque ha demostrado su eficacia en la práctica. El hecho es que debido a su aplicación, una cierta estructura aparece en el proyecto, pero el nivel de anidación de carpetas no es demasiado grande. Su aplicación no conduce a la aparición de algo así como ../../../ en los comandos de importación de componentes, no hace que sea difícil moverse por el proyecto. Este enfoque le permite construir una jerarquía clara de componentes. Ese componente, cuyo nombre coincide con el nombre de la carpeta, se considera "básico". Otros componentes ubicados en la misma carpeta sirven para dividir el componente "base" en partes, lo que simplifica el trabajo con el código de este componente y su soporte.

Aunque soy partidario de la presencia de una determinada estructura de carpetas en el proyecto, creo que lo más importante es la selección de buenos nombres de archivo. Las carpetas en sí mismas son menos importantes.

Usar subcarpetas para subcomponentes


Uno de los inconvenientes del enfoque anterior es que su uso puede dar lugar a la aparición de carpetas de componentes "básicos" que contienen muchos archivos. Considere, por ejemplo, el componente <Product> . Se adjuntarán archivos CSS (hablaremos de ellos más adelante), archivos de prueba, muchos subcomponentes y, posiblemente, otros recursos, como imágenes e íconos SVG. Esta lista de "adiciones" no está limitada. Todo esto caerá en la misma carpeta que el componente "base".

Realmente no me importa eso. Esto me conviene si los archivos tienen nombres bien pensados ​​y si se pueden encontrar fácilmente (utilizando las herramientas de búsqueda de archivos en el editor). Si es así, la estructura de la carpeta se desvanece en el fondo. Aquí hay un tweet sobre este tema.

Sin embargo, si prefiere que su proyecto tenga una estructura más extensa, no hay nada difícil en mover subcomponentes a sus propias carpetas:

 - src/  - components/    - product/      - product.jsx      - ...      - product-price/        - product-price.jsx 

Los archivos de prueba se encuentran en el mismo lugar que los archivos de los componentes bajo prueba.


Comenzamos esta sección con una recomendación simple, que es que los archivos de prueba deben colocarse en el mismo lugar que los archivos con el código que se verificaron con su ayuda. También hablaré sobre cómo prefiero estructurar los componentes, tratando de asegurarme de que estén cerca uno del otro. Pero ahora puedo decir que me parece conveniente colocar los archivos de prueba en las mismas carpetas que los archivos componentes. En este caso, los nombres de los archivos con las pruebas son idénticos a los nombres de los archivos con el código. A los nombres de prueba, antes de la extensión del nombre de archivo, solo se agrega el sufijo .test :

  • Nombre de archivo del componente: auth.js
  • Nombre del archivo de prueba: auth.test.js

Este enfoque tiene varias fortalezas:

  • Facilita la búsqueda de archivos de prueba. De un vistazo, puede comprender si hay una prueba para el componente con el que estoy trabajando.
  • Todos los comandos de importación necesarios son muy simples. En la prueba, para importar el código probado, no es necesario crear estructuras que describan, por ejemplo, la salida de la carpeta __tests__ . Tales equipos se ven extremadamente simples. Por ejemplo, así: import Auth from './auth' .

Si tenemos algunos datos utilizados durante la prueba, por ejemplo, algo así como simulacros de solicitud de API, los colocamos en la misma carpeta donde ya se encuentran el componente y su prueba. Cuando todo lo que se necesita se encuentra en una carpeta, esto contribuye al crecimiento de la productividad. Por ejemplo, si usa una estructura de carpetas ramificadas y el programador está seguro de que existe un determinado archivo, pero no puede recordar su nombre, el programador tendrá que buscar este archivo en muchos subdirectorios. Con el enfoque propuesto, solo mire el contenido de una carpeta y todo quedará claro.

Módulos CSS


Soy un gran fan de los módulos CSS . Descubrimos que son excelentes para crear reglas CSS modulares para componentes.

Además, me gusta mucho la tecnología de componentes con estilo . Sin embargo, en el curso del trabajo en proyectos en los que participaron muchos desarrolladores, resultó que la presencia de archivos CSS reales en el proyecto aumenta la usabilidad.

Como probablemente ya haya adivinado, nuestros archivos CSS se encuentran, como otros archivos, junto a los archivos componentes, en las mismas carpetas. Esto simplifica enormemente el movimiento entre archivos cuando necesita comprender rápidamente el significado de una clase.

Una recomendación más general, cuya esencia impregna todo este material, es que todo el código relacionado con un determinado componente debe mantenerse en la misma carpeta en la que se encuentra este componente. Atrás quedaron los días en que se usaban carpetas separadas para almacenar código CSS y JS, código de prueba y otros recursos. El uso de estructuras de carpetas complejas complica el movimiento entre archivos y no tiene ningún beneficio obvio, excepto que ayuda a "organizar el código". Mantenga los archivos interconectados en la misma carpeta; esto significa pasar menos tiempo moviéndose entre carpetas durante el trabajo.

Incluso creamos un cargador Webpack para CSS, cuyas capacidades corresponden a las características de nuestro trabajo. Comprueba los nombres de clase declarados y arroja un error en la consola si nos referimos a una clase que no existe.

Casi siempre, solo se coloca un código de componente en un archivo


Mi experiencia muestra que los programadores generalmente se adhieren demasiado estrictamente a la regla de que el código para un solo componente React debe estar en un archivo. Al mismo tiempo, apoyo totalmente la idea de que no vale la pena colocar demasiados componentes en un archivo (¡solo imagine las dificultades de nombrar dichos archivos!). Pero creo que no hay nada de malo en colocar el código de un determinado componente "grande" y el código del componente "pequeño" asociado en el mismo archivo. Si tal movimiento ayuda a preservar la pureza del código, si el componente "pequeño" no es demasiado grande para colocarlo en un archivo separado, entonces esto no dañará a nadie.

Por ejemplo, si creo un componente <Product> y necesito un pequeño código para mostrar el precio, entonces puedo hacer esto:

 const Price = ({ price, currency }) => (  <span>    {currency}    {formatPrice(price)}  </span> ) const Product = props => {  // ,      !  return (    <div>      <Price price={props.price} currency={props.currency} />      <div>loads more stuff...</div>    </div>  ) } 

Lo bueno de este enfoque es que no tuve que crear un archivo separado para el componente <Price> , y que este componente está disponible exclusivamente para el componente <Product> . No exportamos este componente, por lo que no se puede importar a otra parte de la aplicación. Esto significa que cuando se le pregunta si debe colocar <Price> en un archivo separado, puede dar una respuesta clara y positiva si necesita importarlo en otro lugar. De lo contrario, puede hacerlo sin poner el código <Price> en un archivo separado.

Carpetas separadas para componentes universales.


Recientemente hemos estado usando componentes universales. De hecho, forman nuestro sistema de diseño (que planeamos publicar algún día), pero hasta ahora hemos comenzado poco a poco, con componentes como <Button> y <Logo> . Un componente se considera "universal" si no está vinculado a una parte específica del sitio, pero es uno de los componentes básicos de la interfaz de usuario.

Componentes similares se encuentran en su propia carpeta ( src/components/generic ). Esto simplifica enormemente el trabajo con todos los componentes universales. Están en un solo lugar, es muy conveniente. Con el tiempo, a medida que el proyecto crece, planeamos desarrollar una guía de estilo (somos grandes fanáticos de react-styleguidist ) para simplificar aún más el trabajo con componentes universales.

Usar alias para importar entidades


La estructura de carpetas relativamente plana en nuestros proyectos garantiza que los comandos de importación no tengan estructuras demasiado largas como ../../ . Pero es difícil prescindir de ellos. Por lo tanto, utilizamos babel-plugin-module-resolver para configurar alias que simplifican los comandos de importación.

Puede hacer lo mismo con Webpack, pero gracias al complemento Babel, los mismos comandos de importación pueden funcionar en las pruebas.

Configuramos esto con un par de alias:

 {  components: './src/components',  '^generic/([\\w_]+)': './src/components/generic/\\1/\\1', } 

El primero es extremadamente simple. Le permite importar cualquier componente, comenzando el comando con la palabra components . En el enfoque normal, los comandos de importación se ven así:

 import Product from '../../components/product/product' 

En cambio, podemos escribirlos así:

 import Product from 'components/product/product' 

Ambos comandos importan el mismo archivo. Esto es muy conveniente, ya que le permite no pensar en la estructura de la carpeta.

El segundo alias es un poco más complicado:

 '^generic/([\\w_]+)': './src/components/generic/\\1/\\1', 

Usamos regex aquí. Encuentra comandos de importación que comienzan con generic (el signo ^ al comienzo de la expresión le permite seleccionar solo aquellos comandos que comienzan con generic ) y captura lo que está después de generic/ en el grupo. Después de eso, usamos el fragmento capturado ( \\1 ) en la construcción ./src/components/generic/\\1/\\1 .

Como resultado, podemos usar los comandos de importación para componentes universales de este tipo:

 import Button from 'generic/button' 

Se convierten a los siguientes comandos:

 import Button from 'src/components/generic/button/button' 

Este comando, por ejemplo, sirve para importar un archivo JSX que describe un botón universal. Hicimos todo esto porque este enfoque simplifica enormemente la importación de componentes universales. Además, nos servirá bien si decidimos cambiar la estructura de los archivos del proyecto (esto, a medida que nuestro sistema de diseño está creciendo, es muy posible).

Aquí me gustaría señalar que debe tener cuidado al trabajar con seudónimos. Si solo tiene unos pocos y están diseñados para resolver problemas de importación estándar, entonces todo está bien. Pero si tiene muchos de ellos, pueden traer más confusión que bien.

Carpeta universal lib para utilidades


Me gustaría recuperar todo el tiempo que pasé tratando de encontrar el lugar perfecto para el código que no es código de componente. Compartí todo esto de acuerdo con diferentes principios, destacando el código de utilidades, servicios, funciones auxiliares. Todo esto tiene tantos nombres que no los mencionaré a todos. Ahora no estoy tratando de averiguar la diferencia entre la "utilidad" y la "función auxiliar" para encontrar el lugar correcto para un determinado archivo. Ahora uso un enfoque mucho más simple y más comprensible: todo esto cae en una sola carpeta lib .

A la larga, el tamaño de esta carpeta puede llegar a ser tan grande que tenga que estructurarlo de alguna manera, pero esto es completamente normal. Siempre es más fácil equipar algo con una determinada estructura que deshacerse de los errores de estructuración excesiva.

En nuestro proyecto Thread, la carpeta lib contiene aproximadamente 100 archivos. Se dividen aproximadamente por igual en archivos que contienen la implementación de ciertas características y en archivos de prueba. No causó ninguna dificultad para encontrar los archivos necesarios. Gracias a los lib/name_of_thing búsqueda inteligentes integrados en la mayoría de los editores, casi siempre tengo que ingresar algo como lib/name_of_thing , y lo que necesito se encuentra.

Además, tenemos un alias que simplifica la importación desde la carpeta lib , permitiéndole usar comandos de este tipo:

 import formatPrice from 'lib/format_price' 

No se alarme por las estructuras de carpetas planas que pueden hacer que varios archivos se almacenen en una carpeta. Por lo general, tal estructura es todo lo que se necesita para un determinado proyecto.

Ocultar bibliotecas de terceros detrás de API nativas


Realmente me gusta el sistema de monitoreo de errores Sentry . A menudo lo usaba al desarrollar partes de aplicaciones de servidores y clientes. Con su ayuda, puede detectar excepciones y recibir notificaciones sobre su ocurrencia. Esta es una gran herramienta que nos permite estar al tanto de los problemas encontrados en el sitio.

Cada vez que uso una biblioteca de terceros en mi proyecto, pienso en cómo hacerlo para que, si es necesario, pueda reemplazarse lo más fácilmente posible con otra cosa. A menudo, como con el mismo sistema Sentry que realmente nos gusta, esto no es necesario. Pero, por si acaso, nunca está de más pensar una manera de evitar el uso de un determinado servicio o una forma de cambiarlo por otro.

La mejor solución a este problema es desarrollar su propia API que oculte las herramientas de otras personas. Esto es algo así como crear un módulo lib/error-reporting.js que exporta la función reportError() . El núcleo de este módulo usa Sentry. Pero Sentry se importa directamente solo en este módulo y en ningún otro lugar. Esto significa que reemplazar Sentry con otra herramienta se verá muy simple. Para hacer esto, será suficiente cambiar un archivo en un lugar. Mientras la API pública de este archivo permanezca sin cambios, el resto del proyecto ni siquiera sabrá que al llamar a reportError() , no se usa Sentry, sino algo más.

Tenga en cuenta que la API pública del módulo se denomina funciones que exporta y sus argumentos. También se denominan la interfaz pública del módulo.

Uso de PropTypes (o herramientas como TypeScript o Flow)


Cuando programo, pienso en tres versiones de mí mismo:

  • Jack del pasado y el código que escribió (código a veces dudoso).
  • Jack de hoy, y el código que escribe ahora.
  • Jack del futuro. Cuando pienso en este futuro, me pregunto el presente sobre cómo puedo escribir código que me facilitará la vida en el futuro.

Puede sonar extraño, pero lo encontré útil, pensando en cómo escribir código, haga la siguiente pregunta: "¿Cómo se percibirá en seis meses?".

Una forma sencilla de hacerse presente y ser más productivo es especificar los tipos de propiedades ( PropTypes ) utilizados por los componentes. Esto ahorrará tiempo buscando posibles errores tipográficos. Esto lo protegerá de situaciones en las que, utilizando el componente, se apliquen propiedades de los tipos incorrectos o se olviden por completo de la transferencia de propiedades. En nuestro caso, la regla eslint-react / prop-types es un buen recordatorio de la necesidad de usar PropTypes .

Si va más allá, se recomienda describir las propiedades con la mayor precisión posible. Por ejemplo, puedes hacer esto:

 blogPost: PropTypes.object.isRequired 

Pero sería mucho mejor hacer esto:

 blogPost: PropTypes.shape({  id: PropTypes.number.isRequired,  title: PropTypes.string.isRequired,  //    }).isRequired 

En el primer ejemplo, se realiza la verificación mínima necesaria. En el segundo, el desarrollador recibe mucha más información útil. Serán muy útiles, por ejemplo, si alguien se olvida de un determinado campo utilizado en el objeto.

Las bibliotecas de terceros se usan solo cuando son realmente necesarias.


Este consejo es más relevante que nunca con la llegada de los ganchos React . Por ejemplo, participé en una gran modificación de una de las partes del sitio Thread y decidí prestar especial atención al uso de bibliotecas de terceros. , , . ( ), . , React-. — , , React API Context, .

, , Redux, . , ( , ). , , , .


— , , . .

 //     emitter.send('user_add_to_cart') //     emitter.on('user_add_to_cart', () => {  //  -  }) 

, . , . , « ». , , , . . «» - , . , , .

Redux. . , . , , user_add_to_cart , . . , , Redux, . , Redux , .

, , , , :

  • , , . .
  • , , . , .
  • , - , . , , .

- , . , , . , , , «» .

, , API Context - .


, (, , ). : , .

, , , . :

 const wrapper = mount(  <UserAuth.Provider value=>    <ComponentUnderTest />  </UserAuth.Provider> ) 

:

 const wrapper = mountWithAuth(ComponentUnderTest, {  name: 'Jack',  userId: 1, }) 

:

  • . — , , , .
  • mountWithAuth . , .

, test-utils.js . , — . .

Resumen


. , , , , . , , . , : . . - , .

Estimados lectores! React-?

Source: https://habr.com/ru/post/460793/


All Articles