Arquitectura de un Proyecto de React, por Ezran Bayantemur

En el siguiente artículo vas a poder ver como un desarrollor soluciona los posibles problemas que se afrontan a la hora de implementar una arquitectura con el framework de React.

Si algún código pudiese no funcionar o estar dudoso, no dudes en consultar el artículo original de Ezran Bayantemur.


He estado desarrollando aplicaciones con React durante mucho tiempo y me encanta cada vez más. React es una biblioteca increíble para crear arquitectura de aplicaciones que ofrece la oportunidad de aplicar principios básicos de software (como SOC, como SOLID) en un proyecto y mantener limpia la base de código incluso si la escala de nuestro proyecto crece.

En este artículo, se habla sobre cómo se puede crear la estructura y la arquitectura de un proyecto con React. Se puede pensar que será un artículo mixto de mejores prácticas y conceptos básicos de React.

Por supuesto que no son “reglas” o algo más, puedes seguir desarrollando como quieras, solamente se busca transmitir ciertas ideas que puedan llegar a serte útiles.

Navegación

La navegación es la columna vertebral de la aplicación. Cuanto más limpio y equilibrado lo mantenga, más fácil de integrar cuando surgen nuevos requisitos, nuevas páginas y mucho menos tiempo para dedicar a “¿Dónde y cómo voy a implementar los nuevos cambios?”.

Al desarrollar una aplicación, toda la arquitectura del proyecto se revela en la fase de diseño. Todas las preguntas como: ¿Qué pantallas serán? ¿Para qué servirá? ¿Cómo se agruparán las páginas en la aplicación? encuentra sus respuestas.

En este punto, puedes crear la arquitectura de navegación. Puedes crear una arquitectura completa mirando los diseños de la pantalla.

Si tu aplicación tiene pantallas con diferentes propósitos, puede reunirlas en una arquitectura Stack separada. Por ejemplo, si la aplicación tiene módulos principales como perfil, mensajería, línea de tiempo puedes crear una estructura similar a esta:

- App
- ProfileStack
- MessageStack
- TimeLineStack
...
...
...
- ProfileStack
- ProfilePage
- UpdatePreferencesPage
- AddNewPhotoPage
- MessageStack
- InboxPage
- NewMessagePage
- TrashCanPage
- TimelineStack
- TimelinePage
- PostPage
- CommentsPage
- LikesPage

El navegador principal tiene pilas de Perfil, Mensaje y Línea de tiempo . De esta forma, los módulos principales de nuestra aplicación son seguros y tienen subpantallas separadas.

Por ejemplo. El módulo MessageStack está relacionado solo con la sección de mensajería, si necesitas alguna pantalla nueva, actualizar solo esa sección hará el trabajo. Podemos navegar desde cualquier pantalla a cualquier lugar. react-navigation nos da la libertad ilimitada al respecto, solo que debemos hacer bien nuestra planificación.

No hay límite para el apilamiento anidado. Los módulos con un contexto similar pueden reunirse en la misma estructura de pila.

Por ejemplo. Si la página de notificación en la sección de configuración contiene 3 de 4 páginas; puede reunirlos en la misma pila. Porque ver las páginas con los nombres NotificationPreferences, NotificationDetail, BlockedAppNotifications en SettingsStack no es una buena acción. Parece que se necesita una pila de notificaciones.

Además, colocarlos así, significa que implementaremos cada nueva página con la misma idea de navegación. Después de todo, deberíamos ceñirnos a un determinado método de desarrollo, ¿verdad? ¿Y si mañana vienen 10 módulos paginados?

Un proyecto muere por no seguir una determinada forma de desarrollo o por seguir una forma de desarrollo incorrecta.

Componentes

Cuando se desarrolla un módulo, las estructuras de sensación de complejidad o las estructuras abiertas para la reutilización deben diseñarse como componentes separados.

Al desarrollar una página o módulo con React, siempre debes considerar dividir. React te da esa oportunidad y debes usarla tanto como puedas.

Un componente actual puede parecer simple hoy, puede que no piense en dividirlo, pero la persona que lo desarrollará después de ti, sigues desarrollándolo así y si ese componente crece como 200-300 loc (líneas de código) la revisión tomara mucho más tiempo que desarrollarlo.

Es como un inodoro, debes dejarlo como quieres encontrarlo.

Entonces, ¿Cuándo deberías dividir un componente?

Al crear el diseño de una aplicación, se selecciona un principio de diseño fijo para que resulte atractivo a la vista. Botones, entradas, modales siempre tienen un diseño consistente y se parecen entre sí.

En lugar de diez diseños de botones diferentes, verías diez variaciones diferentes de un botón. Esto es consistencia, crea la firma de la aplicación en la memoria visual de los usuarios y usted (en realidad, debería) crear su estructura de componentes consistente mientras estos miran diseños.

Por ejemplo; Si hay un diseño de botón que se usa con tanta frecuencia, puede crear su variación y almacenarlo en el directorio de componentes general. También puede almacenar en el mismo directorio los componentes que no se utilizan en ningún otro lugar pero que huelen a reutilizables.

Pero, si hay un componente que usa solo una pantalla, es mejor almacenarlo en el mismo directorio con la pantalla relacionada.

Pongamos un ejemplo. Si los componentes de gráfico y tabla se usarán solo y solo por pantalla de análisis y si se mantendrá completamente en la lógica de análisis, entonces es mejor mantenerlo en el mismo directorio.

Debido a que los módulos son los que se necesitan, los demás deben estar cerca unos de otros. Pero en ese ejemplo, los componentes de botón y modal de lista se pueden almacenar en componentes generales y llamar desde allí.

Entonces, nuestro directorio de archivos será como algo así:

- components
- Button
- Button.tsx
- Button.style.ts
- Button.test.tsx
- Button.stories.tsx
- index.ts
- ListModal
- ListModal.tsx
- ListModal.style.ts
- ListModal.test.tsx
- ListModal.stories.tsx
- index.ts
...
...
- pages
- Analyze
- components
- AnalyzeGraph
- AnalyzeGraph.tsx
- AnalyzeGraph.style.ts
- AnalyzeGraph.test.tsx
- AnalyzeGraph.stories.tsx
- index.ts
- AnalyzeDataTable
- AnalyzeDataTable.tsx
- AnalyzeDataTable.style.ts
- AnalyzeDataTable.test.tsx
- AnalyzeDataTable.stories.tsx
- index.ts
- Analyze.tsx
- Analyze.style.tsx
- index.ts

Los componentes que están relacionados con el módulo de análisis y que solo le servirán se encuentran cerca de ese módulo.

Nota: Al nombrar, dar el nombre del módulo relacionado como prefijo es una opción mucho mejor, creo. Debido a que puede necesitar otro componente de gráfico y tabla en un módulo completamente diferente y si solo da DataTable como nombre, puede tener diez componentes DataTable diferentes y puede tener dificultades para encontrar qué componente se usa en qué módulo.

Una segunda forma: etapa de estilismo

El principio básico más importante para escribir código limpio es dar el nombre correcto a la variable y los valores. Los estilos también son nuestros valores y deben nombrar bien. Mientras escribe un estilo para un componente, cuanto más nombres correctos, más escribe un código mantenible. Porque la persona que continuará desarrollándolo después, encontrará fácilmente qué estilos pertenece a dónde.

Si usa el mismo prefijo con tanta frecuencia al nombrar los estilos, debes considerar esa parte como otro componente.

Entonces, si tu archivo UserBanner.style.ts se ve así, puedes sentir que necesitas un componente como Avatar.tsx .

Porque si hay una etapa de agrupamiento mientras se diseña, significa que se avecina una estructura en crecimiento. No es necesario repetir 3 o 5 veces para considerar una estructura como otro componente. Puedes seguirlo mientras codificas y hace inferencias.

contanier: {...},
title: {...},
inner_container: {...},
avatar_container: {...},
avatar_badge_header: {...},
avatar_title: {...},
input_label:  {...},

Además, no hay ninguna regla para que todos los componentes tengan lógica. Cuanto más dividas el módulo, más lo controlarás y más podrás escribir pruebas.

Hooks

Las estructuras que juegan un papel en el ciclo de vida y representan una lógica de trabajo deben ser abstractas como un gancho.

Para eso, necesitan tener su propia lógica y como en la definición, deberían estar en el ciclo de vida.

La razón principal es la reducción del peso de trabajo en la estructura general y la creación de piezas de trabajo reutilizables. Así como creamos componentes personalizados para reducir la complejidad del código.

Los ganchos personalizados se pueden crear de la misma manera. Lo importante es estar seguro de que la estructura creada y de que funciona correctamente.

¿Cómo entendemos que necesitamos un gancho personalizado?

Expliquemos con un ejemplo. Piensa que necesitas una estructura de búsqueda sobre el alcance del proyecto. Necesitas un componente SearchBox que se pueda usar desde cualquier lugar y usar el paquete fuse.js para la acción de búsqueda.

Primero, implementemos la estructura de búsqueda en dos componentes de ejemplo.

(No guardé los códigos demasiado tiempo, pero puede pensar que las secciones de tres puntos son partes propias del componente)

function ProductPage() {
const fuse = new Fuse<Product>(data, searchOptions);
const [searchKey, setSearchKey] = useState<string>("");
const [searchResult, setSearchResult] = useState<Product[]>([]);
...
...
useEffect(() => {
if (!data) {
return;
}
if (searchKey === "" || typeof searchKey === "undefined") {
return setSearchResult([...data]);
}
const result = fuse.search(searchKey);
if (!result) {
return;
}
setSearchResult(result.map((r) => r.item));
}, [data, searchKey]);
...
...
function search(pattern: string) {
setSearchKey(pattern);
}
...
...
return (
<Layout>
<ProductSearchBox onSearch={setSearchKey} />
<ProductInfo />
...
...
<View>
<ProductDetail />
<List data={searchResult} item={ProductCard} />
</View>
...
...
</Layout>
);
}
export default ProductPage;
function MemberPage() {
const fuse = new Fuse<Member>(data, searchOptions);
const [searchKey, setSearchKey] = useState<string>("");
const [searchResult, setSearchResult] = useState<Member[]>([]);
...
...
useEffect(() => {
if (!data) {
return;
}
if (searchKey === "" || typeof searchKey === "undefined") {
return setSearchResult([...data]);
}
const result = fuse.search(searchKey);
if (!result) {
return;
}
setSearchResult(result.map((r) => r.item));
}, [data, searchKey]);
...
...
function search(pattern: string) {
setSearchKey(pattern);
}
...
...
return (
<Layout>
<MemberSearchBox onSearch={setSearchKey} />
...
...
<View>
<Header />
<List data={searchResult} item={MemberCard} />
</View>
...
...
</Layout>
);
}
export default MemberPage;

Cuando observamos nuestros componentes, lo principal que notamos es que se implementó la misma estructura de búsqueda y se puede ver claramente la repetición del código. Si hay tanta repetición de código en una estructura, eso significa que algo va mal.

Además de eso; cuando alguien abre cualquier archivo, querrá ver solo el código relacionado con el nombre del archivo. Cuando abre el archivo CommentsScreen.tsx , desea ver solo los códigos relacionados con los comentarios, no cualquier otra lógica agrupada.

Sí, en el ejemplo, nuestra estructura de búsqueda está relacionada con los componentes de Producto y Miembro y funcionan para ellos. Pero representan una lógica propia a partir de ahora y, además, se pueden convertir en estructuras reutilizables. Por eso, necesitamos estructuras de componentes o ganchos personalizados.

Volvamos al ejemplo. Hay un uso claro del estado para la acción de búsqueda y ocupa un lugar en el ciclo de vida. Cuando el usuario comienza a escribir en la entrada de búsqueda, esa cadena se almacena en el estado de la clave de búsqueda y cuando se actualiza la lista principal también se filtra.

Entonces, ¿cómo podemos diseñarlo mucho mejor?

Podemos reunir nuestras estructuras de búsqueda en un gancho llamado useSearch. Deberíamos crear un gancho que no dependa de ningún módulo y que tenga una estructura reutilizable para usar libremente en cualquier lugar.

Debido a que usaremos fuse.js para la búsqueda, podemos enviar datos y criterios de búsqueda como entrada y podemos devolver el resultado de la búsqueda y la función de búsqueda que se activará más tarde.

Entonces, el gancho que vamos a crear será este:

interface Props<T> {
data?: Readonly<T[]>;
options?: Fuse.IFuseOptions<T>;
}
interface ReturnType<P> {
search: (s: string) => void;
result?: P[];
}
function useSearch<K>({data, options}: Props<K>): ReturnType<K> {
const fuse = new Fuse<K>(data || [], options);
const [searchKey, setSearchKey] = useState<string>('');
const [searchResult, setSearchResult] = useState<K[]>(data || []);
useEffect(() => {
if (!data) {
return;
}
if (searchKey === '' || typeof searchKey === 'undefined') {
setSearchResult([...data]);
return;
}
const result = fuse.search(searchKey);
if (!result) {
return;
}
setSearchResult(result.map(r => r.item));
}, [data, searchKey]);
function search(pattern: string) {
setSearchKey(pattern);
}
return {search, result: searchResult};
}
export default useSearch;

Con la compatibilidad con TypeScript, nuestro gancho se puede usar con tipos. Con eso podemos enviar y recibir cualquier tipo mientras lo usamos. El flujo de trabajo dentro del gancho es el mismo que hablamos antes, lo verá cuando consulte los códigos.

Si queremos usarlo en nuestros componentes:

function ProductPage() {
const {result, search} = useSearch<Product>(data, searchOptions);
...
...
return (
<Layout>
<ProductSearchBox onSearch={search} />
<ProductInfo />
...
...
<View>
<ProductDetail />
<List data={result} item={ProductCard} />
</View>
...
...
</Layout>
);
}
export default ProductPage;
function MemberPage() {
const {result, search} = useSearch<Member>(data, searchOptions);
...
...
return (
<Layout>
<MemberSearchBox onSearch={search} />
...
...
<View>
<Header />
<List data={result} item={MemberCard} />
</View>
...
...
</Layout>
);
}
export default MemberPage;

Como puedes ver a partir de ahora, la estructura de búsqueda se abstrae de los componentes, tanto la complejidad del código se reduce y siempre que necesitamos una estructura de búsqueda tenemos un gancho personalizado en nuestras manos.

Con eso creamos una estructura mucho más limpia y comprobable.

Por cierto, los ganchos se pueden crear para depender de un contexto o uso genérico como componentes. En ese ejemplo, creamos un gancho personalizado para uso general, pero podemos crear un gancho personalizado para un trabajo o contexto específico.

Por ejemplo, para obtener o manipular datos en una página específica, puede crear su propio gancho y abstraer ese trabajo del componente principal.

Quiero decir:

- hooks
- useSearch
- useSearch.ts
- useSearch.test.tsx
- index.ts
...
...
- pages
- Messages
- hooks
- useMessage
- useMessage.ts
- useMessage.test.tsx
- index.ts
- useReadStatus
- useReadStatus.tsx
- useReadStatus.test.tsx
- index.ts
- Messages.tsx
- Messages.style.tsx
- index.ts

Mientras useSearch usando en la escala del proyecto; useMessage es responsable de la obtención de datos, useReadStatus usa para el estado de lectura del suscriptor en un mensaje. Misma lógica que en los componentes.

Y eso es Hooks ?

En el libro Código limpio; existe una descripción tan buena del requisito de responsabilidad única de una función. Si lo usamos para React: Un componente o un gancho debería hacer una cosa, debería hacerlo solo y debería hacerlo bien.

Contexto

Debes crear una estructura de contexto diferente para los módulos que no pueden comunicarse directamente pero que están conectados desde el contenido.

El contexto no debe considerarse como “todo el envoltorio de todo el proyecto“. Cuando aumenta la complejidad del proyecto. Las estructuras que tienen conexión con la lógica también aumentan en número y estas partes deben mantenerse separadas unas de otras.

El contexto asume el papel de comunicación entre estas partes. Por ejemplo; si necesita comunicación en componentes y páginas del módulo de mensajería; puedes crear una estructura de MessagesContext y crear una lógica de trabajo independiente envolviéndola solo en el módulo de mensajería.

En la misma aplicación, si tienes el módulo Cercano que puedes encontrar amigos a su alrededor y si tiene numerosas partes de trabajo; puede crear NearContext y abstraerlo de los demás.

Entonces, si necesitamos una estructura como, global, accesible en cualquier lugar; ¿No podemos envolver la aplicación principal con un contexto?

Por supuesto que puedes. Es por eso que representa la gestión del estado global.

En este punto, lo principal que debe tener cuidado es no sobrecargar un contexto . No debe envolver la aplicación solo con AppContext y poner todos los estados como información de usuario, tema de estilo y mensajes. Porque ya ha creado módulos de trabajo para ellos y puede ver claramente que son estructuras diferentes.

Además; context actualiza todos los componentes que se conectaron a él en cualquier actualización de estado.

Por ejemplo. Si has creado estados de miembros y mensajes en AppContext y escucha solo el estado de miembros en Profile.tsx y solo el estado de mensajes en el componente MessageList.tsx. Cuando recibes un mensaje nuevo y actualiza el estado de los mensajes. La página de perfil también se actualizará.

Porque escucha el AppContext y hay una actualización sobre el contexto que está relacionado (que en realidad no lo es).

¿Crees que existe realmente una relación entre los mensajes y los módulos de perfil? ¿Por qué se debe realizar una actualización en la sección de perfil cuando llega un mensaje nuevo? Eso significa una actualización innecesaria (renderizar, actualizar, como quieras nombrarlo) y cuando crezcan como una avalancha, causarán muchos problemas de rendimiento.

Por esa razón, debe crear un contexto diferente para diferentes contenidos de trabajo y mantener segura toda la estructura lógica.

Cuando la aplicación da un paso a la fase de mantenimiento, la persona que se ocupará de la actualización de cualquier módulo, debería poder seleccionar el contexto relacionado fácilmente y comprender la arquitectura sin problemas.

En realidad, aquí entra en juego nuevamente la enseñanza más básica del principio del código limpio; el nombre de variable correcto como acabamos de mencionar.

Cuando nombre su contexto de la manera correcta, su estructura también se mantendrá saludable. Porque la persona que ve el UserContext sabrá que debe tomar o poner la información del usuario de aquí. Sabrá no gestionar los trabajos sobre configuración o mensajería desde UserContext. Debido a esto, los principios del código limpio son una disciplina realmente importante.

Además, los usuarios han abierto un problema sobre la API de contexto antes y querían; Los componentes que son estados de escucha del contexto, deben actualizarse solo cuando los estados suscritos se actualizan, al igual que Redux.

Esta respuesta de Dan Abramov en realidad resume muy bien la lógica de trabajo de la API de contexto.

Un componente que escucha un contexto debe necesitar ese contexto. Si ve un estado innecesario al que llamó desde un contexto; esto significa que este estado no tiene lugar en ese Contexto o usted configura esa estructura de Contexto de manera incorrecta. Se trata de la arquitectura que creaste.

Mientras usa Context, asegúrese siempre de que sus componentes realmente necesiten los estados a los que llama. Será menos probable que cometa errores.

Por un pequeño ejemplo:

[ App.tsx ]
<AppProvider> (member, memberPreferences, messages, language)
<Navigation />
</AppProvider>

Si los separamos, que sería mucho mejor.

[ App.tsx ]
<i18nProvider> (language)
<MemberProvider> (member, memberPreferences)  
<Navigation />
</MemberProvider>
</i18nProvider>
...
...
...
[ MessageStack.tsx ]
<MessagesProvider> (messages)
<Stack.Navigator>
<Stack.Screen .../>
<Stack.Screen .../>
<Stack.Screen .../>
</Stack.Navigator>
</MessagesProvider>

Como puede adivinar, dividimos MessagesProvider pero no lo pusimos en el punto de entrada. Debido a que los proveedores i18n y Member son necesarios para el acceso general, Messages se usará solo para el alcance del mensaje y solo activará la actualización de esa parte. Entonces, podemos esperar que el contexto del mensaje actualice la sección del mensaje, ¿verdad?


Fuente Original: Ezran Bayantemur


Artículos Relacionados

Relacionado

The best Udemy courses in August, only for 9.99 $

Los mejores cursos en inglés y en oferta, hasta el día 27. Los cursos incluidos aquí son todos en inglés. Revisa por aquí, entre los mejores cursos en oferta de Udemy de Agosto en español, si lo que deseas son cursos en español. Si por desgracia, no tienes la oportunidad de acceder a comprar alguno de los excelentes cursos que te mostraré a continuación. Siempre puedes ¡SEGUIR LEYENDO!

Ruta de Aprendizaje del Framework React

Como estudiante de desarrollo de aplicaciones, existen muchas librerías y frameworks que pueden ayudarte a desarrollar tus proyectos. Pero cuando se trata de aprender ReactJS, eso no debería ser una decisión difícil de tomar. Existen razones por las que deberías aprender React. ReactJS (también conocido como React) es una biblioteca de código abierto basada en JavaScript. El framework de React es increíblemente popular y se encuentra ¡SEGUIR LEYENDO!

Revery: Framework para crear aplicaciones nativas, de alto rendimiento y multiplataforma con el lenguaje Reason

Revery es como una versión de Electrón nativo súper rápido, con React integrado, Redux y un sistema de construcción rápido, ¡todo listo para funcionar! ¿Qué es Revery? Revery es un framework para aplicaciones GUI multiplataforma de carga rápida de código nativo. Revery proporciona un enfoque funcional similar a React para modelar la interfaz de usuario, así como un andamiaje para administrar el ciclo de vida de ¡SEGUIR LEYENDO!

Remix: React potenciado para lograr páginas web completas

Remix es un framework web de pila completa basado en React que permite al usuario concentrarse en la interfaz de usuario y trabajar a través de los fundamentos web para ofrecer una experiencia de usuario rápida, elegante y resistente. ? WEB OFICIAL DE REMIX Centrado en los fundamentos web y la experiencia de usuario moderna, Remix llega para ofrecer una mejor experiencia en la creación de ¡SEGUIR LEYENDO!

Recursos para Mejorar como Desarrollador Web

Artículos en Inglés Framework Cordoba Desarrollo multiplataforma con Cordova y Electron Prueba de aplicaciones Cordova con Appium Framework Onsen UI Elementos de lista expandibles en Onsen UI 2.10 (2018, Mayo) Vetur para Onsen UI y Element UI (2018, Febrero) Onsen UI Dark & ​​Tema Personalizado (2018, Enero) Framework React Botones de compartir en redes sociales y compartir recuentos para React React bibliotecas en 2021 Recopilatorios de ¡SEGUIR LEYENDO!