Una introducción a React que nos faltaba

React es la biblioteca de JavaScript más popular del mundo. Pero esta biblioteca no es buena porque sea popular, sino porque es popular porque es buena. La mayoría de los tutoriales introductorios de React existentes comienzan con ejemplos de cómo usar la biblioteca. Pero estas guías no dicen nada sobre por qué React es la elección correcta.



Este enfoque tiene sus puntos fuertes. Si alguien se esfuerza por practicar de inmediato mientras domina React, solo necesita mirar la documentación oficial y ponerse a trabajar . Este material ( aquí , si está interesado, su versión en video) está escrito para aquellos que quieren encontrar una respuesta a las siguientes preguntas: “¿Por qué reaccionar? ¿Por qué React funciona de esta manera? ¿Por qué las API de React están diseñadas de la forma en que están? "











¿Por qué reaccionar?



La vida se vuelve más fácil si los componentes desconocen la comunicación de la red, la lógica empresarial de la aplicación o su estado. Dichos componentes, que reciben los mismos parámetros de entrada, siempre forman los mismos elementos visuales.



Cuando apareció la biblioteca React, cambió fundamentalmente la forma en que funcionan los marcos y las bibliotecas de JavaScript. Mientras que otros proyectos similares promovieron las ideas de MVC, MVVM y similares, React adoptó un enfoque diferente. Es decir, aquí la representación del componente visual de la aplicación se aisló de la presentación del modelo. Gracias a React, apareció una arquitectura completamente nueva en el ecosistema frontend de JavaScript: Flux.



¿Por qué hizo esto el equipo de React? ¿Por qué este enfoque es mejor que los anteriores, como la arquitectura MVC y el código espagueti escrito en jQuery? Si es del tipo interesado en estas preguntas, puede ver esta charla de 2013 sobre desarrollo de aplicaciones JavaScript en Facebook.



En 2013, Facebook acaba de completar un trabajo de integración serio en su plataforma de chat. Esta nueva característica se incorporó en casi todas las páginas del proyecto, el chat influyó en los escenarios habituales de trabajo con la plataforma. Era una aplicación compleja incrustada en otra aplicación que no había sido fácil antes. El equipo de Facebook tuvo que lidiar con tareas no triviales, lidiar con la mutación DOM incontrolada y la necesidad de proporcionar una experiencia de usuario asincrónica paralela en el nuevo entorno.



Por ejemplo, ¿cómo saber de antemano qué se mostrará en la pantalla en una situación en la que cualquier cosa, en cualquier momento y por cualquier motivo, puede acceder al DOM y realizar cambios allí? ¿Cómo asegurarse de que lo que ve el usuario esté correctamente trazado?



Usando herramientas populares de front-end que existían antes de React, nada de eso podría garantizarse. En las primeras aplicaciones web, el "estado de carrera" en el DOM era uno de los problemas más comunes.



Falta de determinismo = computación paralela + estado mutable.



Martin Oderski




La principal tarea del equipo de desarrollo de React fue resolver este problema. Lo abordaron con dos enfoques innovadores principales:



  • Enlace de datos unidireccional utilizando la arquitectura Flux.
  • Inmutabilidad del estado del componente. Una vez que se establece el estado de un componente, ya no se puede cambiar. Los cambios de estado no afectan a los componentes renderizados. En cambio, tales cambios conducen a la salida de una nueva vista con un nuevo estado.


La forma más sencilla que encontramos para estructurar y renderizar componentes, desde un punto de vista conceptual, fue simplemente apuntar a una mutación cero.



Tom Ochchino, JSConfUS 2013




La biblioteca React pudo reducir drásticamente el problema de las mutaciones no controladas mediante el uso de la arquitectura Flux. En lugar de adjuntar controladores de eventos para activar actualizaciones DOM en un número arbitrario de objetos (modelos) arbitrarios, la biblioteca React les dio a los desarrolladores la única forma de administrar el estado del componente. Este es el envío de acciones que afectan al almacén de datos. Cuando cambia el estado de la tienda, el sistema solicita que se procese el componente.





La arquitectura de Flux



Cuando me preguntan por qué debería prestar atención a React, respondo de forma sencilla: "El punto es que necesitamos una representación determinista de las vistas, y React facilita mucho esta tarea".



Tenga en cuenta que leer datos del DOM para implementar alguna lógica es un anti-patrón. Quien haga esto va en contra del propósito de usar React. En cambio, los datos deben leerse desde la tienda y las decisiones basadas en esos datos deben tomarse antes de que se procesen los componentes correspondientes.



Si la renderización determinista de componentes fuera lo único de React, entonces eso por sí solo sería una gran innovación. Pero el equipo de desarrollo de React no se detuvo allí. Este equipo presentó al mundo una biblioteca con otras características interesantes y únicas. Y a medida que el proyecto evolucionó, React agregó cosas aún más útiles.



JSX



JSX es una extensión de JavaScript que le permite crear componentes de interfaz de usuario de forma declarativa. JSX tiene las siguientes características notables:





Si, antes de la llegada de JSX, era necesario describir interfaces declarativamente, entonces era imposible hacerlo sin usar plantillas HTML. En aquellos días, no existía un estándar generalmente aceptado para crear tales plantillas. Cada marco utilizó su propia sintaxis. Esta sintaxis tenía que ser aprendida por alguien que, por ejemplo, necesitaba recorrer algunos datos, incrustar valores de variables en una plantilla de texto o decidir qué componente de interfaz mostrar y cuál no.



Hoy en día, si observa diferentes herramientas de front-end, encontrará que no *ngForpuede prescindir de una sintaxis especial, como la directiva de Angular. Pero, dado que JSX se puede llamar un superconjunto de JavaScript, la creación de marcado JSX puede aprovechar las capacidades JS existentes.



Por ejemplo, puede iterar sobre un conjunto de elementos usando el método Array.prototype.map. Puede utilizar operadores lógicos, organizar la representación condicional utilizando el operador ternario. Puede utilizar funciones puras , puede construir cadenas utilizando literales de plantilla . En general, todas las funciones de JavaScript están disponibles para quienes describen interfaces en JSX. Creo que esta es una gran ventaja de React sobre otros marcos y bibliotecas.



Aquí hay un código JSX de muestra:



const ItemList = ({ items }) => (
  <ul>
    {items.map((item) => (
      <li key={item.id}>
        <div>{item.name}</div>
      </li>
    ))}
  </ul>
);


Es cierto que al trabajar con JSX, debe tener en cuenta algunas características que, al principio, pueden parecer inusuales.



  • , , HTML. , class className. camelCase.
  • , , , JSX- key. . id, key.


React no impone al desarrollador la única forma correcta de trabajar con CSS. Por ejemplo, puede pasar un objeto JavaScript con estilos a un componente escribiéndolo en una propiedad style. Con este enfoque, la mayoría de los nombres de estilos familiares serán reemplazados por sus equivalentes camelCase. Pero las posibilidades de trabajar con estilos no se limitan a esto. En la práctica, utilizo simultáneamente diferentes enfoques para diseñar aplicaciones React. El enfoque que elijas depende de lo que quieras peinar. Por ejemplo, utilizo estilos globales para diseñar temas de aplicaciones y diseños de página, y estilos locales para personalizar el aspecto de un componente específico.



Aquí están mis características de estilo favoritas de React:



  • CSS-, . , . — , .
  • CSS- — CSS- . JavaScript-. CSS-, . Next.js, , .
  • El paquete styled-jsx , que le permite declarar estilos directamente en el código de su componente React. Esto es similar a usar una etiqueta <style>en HTML. El alcance de tales estilos se puede llamar "hiperlocal". El caso es que los estilos solo afectan a los elementos a los que se aplican y a sus hijos. Al usar Next.js, el paquete styled-jsx se puede usar sin la necesidad de conectarse y configurar algo usted mismo.


Eventos sintéticos



React nos proporciona un contenedor entre navegadores SyntheticEventsque representa eventos sintéticos y está diseñado para unificar el trabajo con eventos DOM. Los eventos sintéticos son muy útiles por varias razones:



  1. , . .
  2. . , , , JavaScript HTML, . . , , React- .
  3. . . , , . , , , . . , . , JavaScript, .


Tenga en cuenta que debido al uso del grupo de eventos, no se puede acceder a las propiedades de un evento sintético desde una función asincrónica. Para implementar un esquema de trabajo de este tipo, debe tomar datos del objeto de evento y escribirlos en una variable accesible para la función asincrónica.



Ciclo de vida de los componentes



El concepto de ciclo de vida de los componentes de React se centra en proteger el estado del componente. El estado del componente no debe cambiar mientras se muestra. Esto se logra gracias al siguiente esquema de trabajo: el componente está en un estado determinado y renderizado. Luego, gracias a los eventos del ciclo de vida, es posible aplicarle efectos, puede influir en su estado, trabajar con eventos.



Comprender el ciclo de vida de los componentes de React es extremadamente importante para desarrollar interfaces y, al mismo tiempo, no luchar con React, sino usar esta biblioteca según lo previsto por sus desarrolladores. Las "batallas" con React, como cambiar incorrectamente el estado de los componentes o leer datos del DOM, niegan las fortalezas de esta biblioteca.



En React, comenzando con la versión 0.14, había una sintaxis de descripción de componentes basada en clases que le permite manejar eventos del ciclo de vida de los componentes. Hay tres etapas críticas en el ciclo de vida de los componentes de React: Montar, Actualizar y Desmontar.





Ciclo de vida del componente



La etapa de actualización se puede dividir en tres partes: Render (renderizado), Precommit (preparación para realizar cambios en el árbol DOM), Commit (realizar cambios en el árbol DOM).





La estructura de la etapa de actualización



Detengámonos en estas etapas del ciclo de vida del componente con más detalle:



  • Render — . render() , . , JSX.
  • Precommit — DOM, getSnapShotBeforeUpdate. , , .
  • Compromiso: durante esta fase del ciclo de vida del componente, React actualiza el DOM y las referencias . Aquí puede utilizar un método componentDidUpdateo un gancho useEffect. Aquí es donde puede realizar efectos, programar actualizaciones, usar el DOM y otras tareas similares.


Dan Abramov ha preparado un diagrama excelente que ilustra cómo funcionan los mecanismos del ciclo de vida de los componentes.





El ciclo de vida de los componentes de React



Creo que representar componentes como clases de larga duración no es el mejor modelo mental de React. Recuerde que el estado de los componentes de React no debe mutar. El estado obsoleto debe reemplazarse por uno nuevo. Cada uno de estos reemplazos hace que el componente se vuelva a renderizar. Esto le da a React lo que podría decirse que es su característica más importante y valiosa: soporte para un enfoque determinista para renderizar componentes visuales.



Es mejor pensar en este comportamiento de la siguiente manera: cada vez que el componente se procesa, la biblioteca llama a una función determinista que devuelve JSX. Esta función no debería invocar sus propios efectos secundarios por sí sola. Pero ella, si lo necesita, puede pasar solicitudes a React para realizar tales efectos.



En otras palabras, tiene sentido pensar en la mayoría de los componentes de React como funciones puras que toman parámetros de entrada y devuelven JSX. Las funciones puras tienen las siguientes características:



  • Cuando se les da la misma entrada, siempre devuelven la misma salida (son deterministas).
  • No tienen efectos secundarios (es decir, no funcionan con recursos de red, no envían nada a la consola, no escriben nada, localStorageetc.).


Tenga en cuenta que si se necesitan efectos secundarios para que un componente funcione, puede ejecutarlos utilizando useEffecto haciendo referencia al creador de acciones pasado al componente a través de accesorios y permitiendo que los efectos secundarios se manejen fuera del componente .



Reaccionar ganchos



React 16.8 introduce un nuevo concepto llamado React hooks. Estas son funciones que le permiten conectarse a eventos del ciclo de vida de los componentes sin usar la sintaxis de clase y sin depender de los métodos del ciclo de vida de los componentes. Como resultado, fue posible crear componentes no en forma de clases, sino en forma de funciones.



Llamar a un gancho, en general, significa un efecto secundario, uno que permite que el componente funcione con su estado y con el subsistema de E / S. Un efecto secundario es cualquier cambio de estado visible fuera de la función, excepto por un cambio en el valor devuelto por la función. Uso del



gancho Efectole permite poner en cola los efectos secundarios para su posterior ejecución. Se llamarán en el momento apropiado del ciclo de vida del componente. Este momento puede llegar inmediatamente después de que se monte el componente (por ejemplo, cuando se llama al método de ciclo de vida componentDidMount ), durante la fase Commit (método componentDidUpdate ), justo antes de que se desmonte el componente ( componentWillUnmount ).



¿Observa que hay tres métodos de ciclo de vida de componentes asociados con un gancho? El punto aquí es que los ganchos le permiten combinar lógica relacionada, y no "diseñarla", como estaba antes, de acuerdo con diferentes métodos del ciclo de vida del componente.



Muchos componentes deben realizar alguna acción mientras están montados, algo debe actualizarse cada vez que se vuelve a dibujar el componente y los recursos deben liberarse inmediatamente antes de desmontar el componente para evitar pérdidas de memoria. Gracias al uso, useEffecttodas estas tareas se pueden resolver en una sola función, sin dividir su solución en 3 métodos diferentes, sin mezclar su código con el código de otras tareas que no están relacionadas con ellas, pero que también necesitan estos métodos.



Esto es lo que nos dan los ganchos de React:



  • Le permiten crear componentes que se representan como funciones en lugar de clases.
  • Te ayudan a organizar mejor tu código.
  • Facilitan compartir la misma lógica entre diferentes componentes.
  • Se pueden crear nuevos ganchos componiendo ganchos existentes (llamándolos desde otros ganchos).


En general, recomendamos utilizar componentes funcionales y ganchos en lugar de componentes basados ​​en clases. Los componentes funcionales suelen ser más compactos que los componentes basados ​​en clases. Su código está mejor organizado, es más legible, más reutilizable y más fácil de probar.



Componentes de contenedores y componentes de presentación



En un esfuerzo por mejorar la modularidad de los componentes y su reutilización, me concentro en desarrollar dos tipos de componentes:



  • Los componentes del contenedor son componentes que están conectados a fuentes de datos y pueden tener efectos secundarios.
  • Los beans de presentación son, en su mayor parte, beans puros que, dados los mismos accesorios y contexto, siempre devuelven el mismo JSX.


Los componentes puros no deben confundirse con la clase base React.PureComponent , que se llama así porque no es seguro usar para crear componentes que no son puros.



▍Componentes de presentación



Considere las características de los componentes de la presentación:



  • No interactúan con los recursos de la red.
  • No guardan datos localStorageni cargan desde allí.
  • No dan algunos datos impredecibles.
  • No se refieren directamente a la hora actual del sistema (por ejemplo, llamando a un método Date.now()).
  • No interactúan directamente con el almacén de estado de la aplicación.
  • - , , , .


Es debido al último elemento de esta lista que mencioné, cuando hablaba de componentes de presentación, que estos son en su mayoría componentes puros. Estos componentes leen su estado del estado global de React. Por lo tanto, al igual que los ganchos useStatey useReducerproporcionarles ciertos datos implícitos (es decir, datos que no se describen en la función de firma) que, desde un punto de vista técnico, no pueden llamar a dichos componentes como "limpios". Si necesitas que estén realmente limpios, puedes delegar todas las tareas de gestión del estado al componente contenedor, pero supongo que no deberías hacer esto, al menos hasta que se pueda verificar el correcto funcionamiento del componente usando modular pruebas.



Mejor al enemigo de lo bueno.



Voltaire




▍ Componentes del contenedor



Los componentes del contenedor son aquellos componentes que son responsables de administrar el estado, realizar operaciones de E / S o cualquier otra tarea que pueda ser un efecto secundario. No tienen que generar ningún marcado por sí mismos. En cambio, delegan la tarea de renderizado a los componentes de presentación y ellos mismos sirven como envoltorio para dichos componentes. Normalmente, un componente contenedor en una aplicación React + Redux simplemente llama mapStateToProps()y mapDispatchToProps()luego pasa los datos apropiados a los componentes de presentación. Los contenedores también se pueden utilizar para algunas tareas generales, que analizaremos a continuación.



Componentes de orden superior



Un componente de orden superior (HOC) es un componente que toma otros componentes y devuelve un nuevo componente que implementa una nueva funcionalidad basada en los componentes originales.



Los componentes de orden superior funcionan envolviendo algunos componentes con otros. Un componente contenedor puede implementar algo de lógica y crear elementos DOM. Puede pasar o no accesorios adicionales al componente envuelto.



A diferencia de React hooks y render props, los componentes de orden superior se prestan a la composición utilizando el enfoque estándar para la composición de funciones. Esto le permite describir de forma declarativa los resultados de la composición de las capacidades previstas para su uso en diferentes lugares de la aplicación. Al mismo tiempo, los componentes confeccionados no deben ser conscientes de la existencia de ciertas posibilidades. Aquí hay un ejemplo de HOC de EricElliottJS.com :



import { compose } from 'lodash/fp';
import withFeatures from './with-features';
import withEnv from './with-env';
import withLoader from './with-loader';
import withCoupon from './with-coupon';
import withLayout from './with-layout';
import withAuth from './with-auth';
import { withRouter } from 'next/router';
import withMagicLink from '../features/ethereum-authentication/with-magic-link';

export default compose(
  withEnv,
  withAuth,
  withLoader,
  withLayout({ showFooter: true }),
  withFeatures,
  withRouter,
  withCoupon,
  withMagicLink,
);


Aquí se muestra una combinación de muchas características compartidas en todas las páginas del sitio. Es decir, withEnvlee la configuración de las variables de entorno, withAuthimplementa el mecanismo de autenticación de GitHub, withLoadermuestra la animación mientras carga los datos del usuario, , withLayout({ showFooter: true })muestra un diseño estándar con un pie de página, withFeaturemuestra la configuración, withRoutercarga el enrutador, withCoupones responsable de trabajar con cupones y withMagicLingadmite la autenticación de usuario sin una contraseña usando Magia .



Por cierto, dado que la autenticación de contraseña está desactualizada y es una práctica peligrosa, vale la pena utilizar otros métodos de autenticación de usuario en estos días.



Casi todas las páginas del sitio mencionado anteriormente aprovechan todas estas características. Teniendo en cuenta que están compuestos por medio de un componente de orden superior, puede incluirlos todos en un componente contenedor con solo una línea de código. Por ejemplo, así es como se vería la página del tutorial:



import LessonPage from '../features/lesson-pages/lesson-page.js';
import pageHOC from '../hocs/page-hoc.js';
export default pageHOC(LessonPage);


Estos componentes de orden superior tienen una alternativa, pero esta es una construcción dudosa llamada "pirámide de la fatalidad" y es mejor no usarla. Así es como se ve:



import FeatureProvider from '../providers/feature-provider';
import EnvProvider from '../providers/env-provider';
import LoaderProvider from '../providers/loader-provider';
import CouponProvider from '../providers/coupon-provider';
import LayoutProvider from '../providers/layout-provider';
import AuthProvider from '../providers/auth-provider';
import RouterProvider from '../providers/RouterProvider';
import MagicLinkProvider from '../providers/magic-link-provider';
import PageComponent from './page-container';

const WrappedComponent = (...props) => (
  <EnvProvider { ...props }>
    <AuthProvider>
      <LoaderProvider>
        <LayoutProvider showFooter={ true }>
          <FeatureProvider>
            <RouterProvider>
              <CouponProvider>
                <MagicLinkProvider>
                  <YourPageComponent />
                </MagicLinkProvider>
              </CouponProvider>
            </RouterProvider>
          </FeatureProvider>
        </LayoutProvider>
      </LoaderProvider>
    </AuthProvider>
  </EnvProvider>
);


Y esto tendrá que repetirse en cada página. Y si es necesario cambiar algo en esta estructura, habrá que realizar cambios allí donde esté presente. Creo que las desventajas de este enfoque son bastante obvias.



Usar la composición para resolver problemas generales es una de las mejores formas de reducir la complejidad del código de su aplicación. La composición es tan importante que incluso escribí un libro al respecto .



Salir



  • ¿Por qué reaccionar? React nos brinda una representación determinista de las representaciones visuales de los componentes, que se basa en el enlace de datos unidireccionales y el estado inmutable de los componentes.
  • JSX nos permite describir fácilmente interfaces mediante declaración en código JavaScript.
  • - .
  • . , . , DOM DOM.
  • React , . , , .
  • - . - .
  • , . ( , ).


?



En este artículo de React, hemos cubierto muchos conceptos de programación funcional. Si se está esforzando por comprender en profundidad los principios del desarrollo de aplicaciones React, le resultará útil repasar sus conocimientos sobre funciones puras , sobre inmutabilidad , sobre curación y aplicación parcial de funciones, sobre composición de funciones. Puede encontrar materiales relacionados en EricElliottJS.com .



Recomiendo usar React junto con Redux , Redux-Saga y RITEway . Se recomienda Redux para usar con Autodux e Immer... Para organizar esquemas de estado complejos, puede intentar usar Redux-DSM .



Cuando aprenda lo básico y esté listo para crear aplicaciones React reales, eche un vistazo a Next.js y Vercel . Estas herramientas ayudarán a automatizar la configuración del sistema de construcción del proyecto y la canalización de CI / CD, con su ayuda puede preparar el proyecto para una implementación optimizada en el servidor. Tienen el mismo efecto que todo un equipo de DevOps, pero son completamente gratuitos.



¿Qué herramientas auxiliares usas al desarrollar aplicaciones React?










All Articles