Cómo escalar las aplicaciones de React

 

 

 

  • Creación y mantenimiento de sistemas de diseño exitosos, con Brad Fost

  • Índice
    1. Contenedores y componentes
    2. Estructura
    3. Estilo
      1. Nombres de clases únicos
    4. Restablecer propiedades para cada componente
    5. Obtención de datos
      1. Usando redux-saga como mortero
    6. Resumen
      1. Otras lecturas

    Tradicionalmente, el escalado era principalmente relevante para los sistemas del lado del servidor. A medida que más y más usuarios usaran su aplicación, necesitaba asegurarse de poder agregar más servidores a su clúster, de que su base de datos pudiera dividirse en varios servidores, etc. Debido a las aplicaciones web enriquecidas, el escalado se ha convertido en un tema importante en el frontend. La interfaz de una aplicación compleja debe poder manejar una gran cantidad de usuarios, desarrolladores y partes. En este artículo, Max Stoiber comparte todo lo que necesita sobre React Boilerplate para comenzar.

     

    Recientemente lanzamos la versión 3 de React Boilerplate , uno de los kits de inicio de React más populares, después de varios meses de trabajo. El equipo habló con cientos de desarrolladores sobre cómo crean y escalan sus aplicaciones web y quiero compartir algunas cosas que aprendimos a lo largo del camino.

    Nos dimos cuenta desde el principio del proceso de que no queríamos que fuera "simplemente otro modelo estándar". Queríamos brindarles a los desarrolladores que estaban iniciando una empresa o creando un producto la mejor base para comenzar y escalar.

    Tradicionalmente, el escalado era principalmente relevante para los sistemas del lado del servidor. A medida que más y más usuarios usaran su aplicación, necesitaba asegurarse de poder agregar más servidores a su clúster, de que su base de datos pudiera dividirse en varios servidores, etc.

    Hoy en día, debido a las aplicaciones web enriquecidas, el escalado también se ha convertido en un tema importante en el front-end. La interfaz de una aplicación compleja debe poder manejar una gran cantidad de usuarios, desarrolladores y partes. Es necesario tener en cuenta estas tres categorías de escalamiento (usuarios, desarrolladores y partes); de lo contrario, habrá problemas en el futuro.

     

    Contenedores y componentes

    La primera gran mejora en la claridad para las grandes aplicaciones es la diferenciación entre componentes con estado (“contenedores”) y sin estado (“componentes”) . Los contenedores administran datos o están conectados al estado y generalmente no tienen estilos asociados. Por otro lado, los componentes tienen un estilo asociado y no son responsables de ningún dato o gestión de estado. Al principio encontré esto confuso. Básicamente, los contenedores son responsables de cómo funcionan las cosas y los componentes son responsables de cómo se ven las cosas.

    Dividir nuestros componentes de esta manera nos permite separar limpiamente los componentes reutilizables y las capas intermedias de gestión de datos. Como resultado, puede ingresar y editar sus componentes con confianza sin preocuparse de que sus estructuras de datos se estropeen, y puede editar sus contenedores sin preocuparse de que el estilo se estropee. Razonar y trabajar con su aplicación se vuelve mucho más fácil de esa manera, ¡la claridad mejora enormemente!

    Estructura

    Tradicionalmente, los desarrolladores estructuraban sus aplicaciones React por tipo. Esto significa que tenían carpetas como actions/, components/, containers/, etc.

    Imagine un contenedor de barra de navegación llamado NavBar. Tendría algún estado asociado y una toggleNavacción que lo abre y lo cierra. Así es como se estructurarían los archivos al agruparlos por tipo:

    react-app-by-type ├── css ├── actions │ └── NavBarActions.js ├── containers │ └── NavBar.jsx ├── constants │ └── NavBarConstants.js ├── components │ └── App.jsx └── reducers └── NavBarReducer.js

    Si bien esto funciona bien por ejemplo, una vez que tienes cientos o potencialmente miles de componentes, el desarrollo se vuelve muy difícil. Para agregar una función, tendría que buscar el archivo correcto en media docena de carpetas diferentes con miles de archivos. Esto rápidamente se volvería tedioso y la confianza en el código base disminuiría.

    Después de una larga discusión en nuestro rastreador de problemas de GitHub y de probar un montón de estructuras diferentes, creemos que hemos encontrado una solución mucho mejor:

    En lugar de agrupar los archivos de su aplicación por tipo, ¡ agrúpelos por característica ! Es decir, coloque todos los archivos relacionados con una función (por ejemplo, la barra de navegación) en la misma carpeta.

    Veamos cómo se vería la estructura de carpetas en nuestro NavBarejemplo:

    react-app-by-feature ├── css ├── containers │└── NavBar │├── NavBar.jsx │├── actions.js │├── constants.js │└── reducer.js └── components └── App.jsx

    Los desarrolladores que trabajen en esta aplicación necesitarían ingresar a una sola carpeta para trabajar en algo. Y necesitarían crear solo una carpeta para agregar una nueva función. Cambiar el nombre es fácil con buscar y reemplazar, y cientos de desarrolladores podrían trabajar en la misma aplicación a la vez sin causar ningún conflicto.

     

    Cuando leí por primera vez sobre esta forma de escribir aplicaciones React, pensé: "¿Por qué iba a hacer eso?" ¡La otra forma funciona absolutamente bien! Sin embargo, me enorgullezco de mantener la mente abierta, así que lo probé en un proyecto pequeño. Me enamoré en 15 minutos. Mi confianza en el código base era inmensa y, con la división contenedor-componente, trabajar en él fue muy sencillo.

    Es importante tener en cuenta que esto no significa que las acciones redux y los reductores solo se puedan usar en ese componente. ¡Pueden (y deben) importarse y usarse desde otros componentes!

    Sin embargo, dos preguntas me vinieron a la cabeza mientras trabajaba así: "¿Cómo manejamos el estilo?" y "¿Cómo manejamos la obtención de datos?" Permítanme abordarlos por separado.

    Estilo

    Aparte de las decisiones arquitectónicas, trabajar con CSS en una arquitectura basada en componentes es difícil debido a dos propiedades específicas del propio lenguaje: nombres globales y herencia.

    Nombres de clases únicos

    Imagine este CSS en algún lugar de una aplicación grande:

    .header { /* … */ }.title { background-color: yellow;}

    Inmediatamente reconocerás un problema: titlees un nombre muy genérico. Otro desarrollador (o tal vez incluso el mismo algún tiempo después) podría entrar y escribir este código:

    .footer { /* … */ }.title { border-color: blue;}

    Esto creará un conflicto de nombres y, de repente, tu título tendrá un borde azul y un fondo amarillo en todas partes, ¡y tendrás que buscar en miles de archivos para encontrar la única declaración que lo ha estropeado todo!

    Afortunadamente, algunos desarrolladores inteligentes han encontrado una solución a este problema, a la que llamaron Módulos CSS . La clave de su enfoque es ubicar los estilos de un componente en su carpeta :

     react-app-with-css-modules ├── containers └── components └── Button ├── Button.jsx └── styles.css

    El CSS se ve exactamente igual, excepto que no tenemos que preocuparnos por convenciones de nomenclatura específicas y podemos darle a nuestro código nombres bastante genéricos:

    .button { /* … */}

    Luego require(o import) estos archivos CSS en nuestro componente y asignamos nuestra etiqueta JSX a classNamede styles.button:

    /* Button.jsx */var styles = require('./styles.css');div className={styles.button}/div

    Si ahora miras el DOM en el navegador, ¡lo verás div/div! CSS Modules se encarga de “uniquificar” los nombres de nuestras clases anteponiendo el nombre de la aplicación y posponiendo un breve hash del contenido de la clase. Esto significa que la posibilidad de que las clases se superpongan es casi nula, y si se superponen, tendrán el mismo contenido de todos modos (porque el hash, es decir, el contenido, tiene que ser el mismo).

     

    Restablecer propiedades para cada componente

    En CSS, ciertas propiedades se heredan entre nodos. Por ejemplo, si el nodo padre tiene un line-heightconjunto y el hijo no tiene nada especificado, automáticamente se aplicará lo mismo line-heightque el padre.

    En una arquitectura basada en componentes, eso no es lo que queremos. Imagine un Headercomponente y un Footercomponente con estos estilos:

    .header { line-height: 1.5em; /* … */}.footer { line-height: 1; /* … */}

    Digamos que renderizamos Buttondentro de estos dos componentes y, de repente, ¡nuestros botones se ven diferentes en el encabezado y pie de página de nuestra página! Esto es cierto no sólo para line-height: Se heredarán alrededor de una docena de propiedades CSS, y rastrear y eliminar esos errores en su aplicación sería muy difícil. Blog de divulgación científica

    En el mundo del front-end, es bastante común utilizar una hoja de estilos restablecida para normalizar los estilos en todos los navegadores. ¡Las opciones populares incluyen Restablecer CSS, Normalize.css y sanitize.css! ¿Qué pasaría si tomáramos ese concepto y reiniciáramos cada componente ?

    ¡Esto se llama reinicio automático y existe como un complemento para PostCSS ! Si agrega PostCSS Auto Reset a sus complementos PostCSS, hará exactamente esto: ajustará un reinicio local alrededor de cada componente, configurando todas las propiedades heredables a sus valores predeterminados para anular las herencias.

    Obtención de datos

    El segundo problema asociado con esta arquitectura es la obtención de datos. La ubicación conjunta de sus acciones en sus componentes tiene sentido para la mayoría de las acciones, pero la obtención de datos es inherentemente una acción global que no está vinculada a un solo componente.

    La mayoría de los desarrolladores utilizan actualmente Redux Thunk para gestionar la obtención de datos con Redux. Una acción típica de thunk se vería así:

    /* actions.js */function fetchData() { return function thunk(dispatch) { // Load something asynchronously. fetch('https://someurl.com/somendpoint', function callback(data) { // Add the data to the store. dispatch(dataLoaded(data)); }); }}

    Esta es una manera brillante de permitir la recuperación de datos de las acciones, pero tiene dos puntos débiles: probar esas funciones es muy difícil y, conceptualmente, tener la recuperación de datos en las acciones no parece del todo correcto.

    Una gran ventaja de Redux son los creadores de acción pura, que son fácilmente comprobables. Al devolver un procesador de una acción, de repente tienes que realizar una doble llamada a la acción, burlarte de la dispatchfunción, etc.

    Recientemente, un nuevo enfoque ha arrasado en el mundo de React: redux-saga . redux-saga utiliza las funciones del generador de Esnext para hacer que el código asincrónico parezca sincrónico y hace que esos flujos asincrónicos sean muy fáciles de probar. El modelo mental detrás de las sagas es que son como un hilo separado en su aplicación que maneja todas las cosas asincrónicas, ¡sin molestar al resto de la aplicación!

     

    Permítanme ilustrarlo con un ejemplo:

    /* sagas.js */import { call, take, put } from 'redux-saga/effects';// The asterisk behind the function keyword tells us that this is a generator.function* fetchData() { // The yield keyword means that we'll wait until the (asynchronous) function // after it completes. // In this case, we wait until the FETCH_DATA action happens. yield take(FETCH_DATA); // We then fetch the data from the server, again waiting for it with yield // before continuing. var data = yield call(fetch, 'https://someurl.com/someendpoint'); // When the data has finished loading, we dispatch the dataLoaded action. put(dataLoaded(data));}

    No se asuste por el código de aspecto extraño: ¡esta es una manera brillante de manejar flujos asincrónicos!

    El código fuente anterior casi se lee como una novela, evita el infierno de las devoluciones de llamadas y, además, es fácil de probar . Ahora bien, quizás te preguntes, ¿por qué es fácil realizar pruebas? La razón tiene que ver con nuestra capacidad para probar los "efectos" que redux-saga exporta sin necesidad de completarlos.

    Estos efectos que importamos en la parte superior del archivo son controladores que nos permiten interactuar fácilmente con nuestro código redux:

    • put()despacha una acción de nuestra saga.
    • take()pausa nuestra saga hasta que ocurre una acción en nuestra aplicación.
    • select()obtiene una parte del estado redux (algo así como mapStateToProps).
    • call()llama a la función pasada como primer argumento con los argumentos restantes.

    ¿Por qué son útiles estos efectos? Veamos cómo se vería la prueba para nuestro ejemplo:

    /* sagas.test.js */var sagaGenerator = fetchData();describe('fetchData saga', function() { // Test that our saga starts when an action is dispatched, // without having to simulate that the dispatch actually happened! it('should wait for the FETCH_DATA action', function() { expect(sagaGenerator.next()).to.equal(take(FETCH_DATA)); }); // Test that our saga calls fetch with a specific URL, // without having to mock fetch or use the API or be connected to a network! it('should fetch the data from the server', function() { expect(sagaGenerator.next()).to.equal(call(fetch, 'https://someurl.com/someendpoint')); }); // Test that our saga dispatches an action, // without having to have the main application running! it('should dispatch the dataLoaded action when the data has loaded', function() { expect(sagaGenerator.next()).to.equal(put(dataLoaded())); });});

    Los generadores de Esnext no pasan de la yieldpalabra clave hasta que generator.next()se llama, momento en el que ejecutan la función, ¡hasta que encuentran la siguiente yieldpalabra clave! Al utilizar los efectos de redux-saga, podemos probar fácilmente cosas asincrónicas sin necesidad de burlarnos de nada y sin depender de la red para nuestras pruebas.

     

    Por cierto, también ubicamos los archivos de prueba en los archivos que estamos probando. ¿Por qué deberían estar en una carpeta separada? De esa manera, todos los archivos asociados con un componente están realmente en la misma carpeta, ¡incluso cuando estamos probando cosas!

    Si crees que aquí es donde terminan los beneficios de redux-saga, ¡estás equivocado! De hecho, hacer que la obtención de datos sea fácil, atractiva y comprobable podría ser su menor beneficio.

    Usando redux-saga como mortero

    Nuestros componentes ahora están desacoplados . No les importa ningún otro estilo o lógica; se preocupan únicamente por sus propios asuntos... bueno, casi.

    Imaginemos un componente Clocky un Timer. Cuando se presiona un botón en el reloj, queremos iniciar el cronómetro; y cuando se presiona el botón de parada del temporizador, desea mostrar la hora en el reloj.

    Convencionalmente, podrías haber hecho algo como esto:

    /* Clock.jsx */import { startTimer } from '../Timer/actions';class Clock extends React.Component { render() { return ( /* … */ button onClick={this.props.dispatch(startTimer())} / /* … */ ); }}
    /* Timer.jsx */import { showTime } from '../Clock/actions';class Timer extends React.Component { render() { return ( /* … */ button onClick={this.props.dispatch(showTime(currentTime))} / /* … */ ); }}

    De repente, no puedes usar esos componentes por separado y reutilizarlos se vuelve casi imposible.

    En cambio, podemos usar redux-saga como "mortero" entre estos componentes desacoplados, por así decirlo. Al escuchar ciertas acciones, podemos reaccionar (juego de palabras) de diferentes maneras, dependiendo de la aplicación, lo que significa que nuestros componentes ahora son verdaderamente reutilizables.

    Primero arreglemos nuestros componentes:

    /* Clock.jsx */import { startButtonClicked } from '../Clock/actions';class Clock extends React.Component { /* … */ button onClick={this.props.dispatch(startButtonClicked())} / /* … */}
    /* Timer.jsx */import { stopButtonClicked } from '../Timer/actions';class Timer extends React.Component { /* … */ button onClick={this.props.dispatch(stopButtonClicked(currentTime))} / /* … */}

    ¡Observe cómo cada componente se preocupa solo por sí mismo e importa solo sus propias acciones!

    Ahora, usemos una saga para volver a unir esos dos componentes desacoplados:

    /* sagas.js */import { call, take, put, select } from 'redux-saga/effects';import { showTime } from '../Clock/actions';import { START_BUTTON_CLICKED } from '../Clock/constants';import { startTimer } from '../Timer/actions';import { STOP_BUTTON_CLICKED } from '../Timer/constants';function* clockAndTimer() { // Wait for the startButtonClicked action of the Clock // to be dispatched. yield take(START_BUTTON_CLICKED); // When that happens, start the timer. put(startTimer()); // Then, wait for the stopButtonClick action of the Timer // to be dispatched. yield take(STOP_BUTTON_CLICKED); // Get the current time of the timer from the global state. var currentTime = select(function (state) { return state.timer.currentTime }); // And show the time on the clock. put(showTime(currentTime));}

    Hermoso.

    Resumen

    Estas son las conclusiones clave que debe recordar:

    • Diferenciar entre contenedores y componentes.
    • Estructura tus archivos por característica.
    • Utilice módulos CSS y reinicio automático de PostCSS.
    • Utilice redux-saga para:
      • tener flujos asincrónicos legibles y comprobables,
      • une tus componentes desacoplados.

    Otras lecturas

    • Por qué debería considerar React Native para su aplicación móvil
    • Automatización de pruebas para aplicaciones, juegos y la web móvil
    • Representación del lado del servidor con React, Node y Express
    • Notas sobre la accesibilidad renderizada por el cliente

    (il, vf, al, mrn)Explora más en

    • Codificación
    • Herramientas
    • javascript
    • Reaccionar





    Tal vez te puede interesar:

    1. ¿Deberían abrirse los enlaces en ventanas nuevas?
    2. 24 excelentes tutoriales de AJAX
    3. 70 técnicas nuevas y útiles de AJAX y JavaScript
    4. Más de 45 excelentes recursos y repositorios de fragmentos de código

    Cómo escalar las aplicaciones de React

    Cómo escalar las aplicaciones de React

    Creación y mantenimiento de sistemas de diseño exitosos, con Brad Fost Índice Contenedores y componentes

    programar

    es

    https://pseint.es/static/images/programar-como-escalar-las-aplicaciones-de-react-901-0.jpg

    2024-04-04

     

    Cómo escalar las aplicaciones de React
    Cómo escalar las aplicaciones de React

     

     

    Top 20