Problemas de los patrones básicos para crear aplicaciones basadas en datos en React.JS

Para crear interfaces, React recomienda el uso de bibliotecas de composición y administración de estado para construir jerarquías de componentes. Sin embargo, con patrones de composición complejos, aparecen problemas:


  1. Necesidad de estructurar elementos secundarios innecesariamente
  2. O páselos como accesorios, lo que complica la legibilidad, la semántica y la estructura del código

Para la mayoría de los desarrolladores, el problema puede no ser obvio y lo arrojan al nivel de la administración estatal. Esto también se discute en la documentación de React:


Si desea deshacerse de pasar algunos accesorios a muchos niveles, generalmente la composición de componentes es una solución más simple que el contexto.
Reaccionar documentación Contexto

Si seguimos el enlace, veremos otro argumento:


En Facebook, usamos React en miles de componentes, y no hemos encontrado ningún caso en el que recomendaríamos crear jerarquías de herencia de componentes.
Reaccionar documentación Composición versus herencia.

Por supuesto, si todos los que usan la herramienta leen la documentación y reconocen la autoridad de los autores, esta publicación no lo habría sido. Por lo tanto, analizamos los problemas de los enfoques existentes.


Patrón # 1 - Componentes controlados directamente



Comencé con esta solución debido al marco Vue que recomienda este enfoque . Tomamos la estructura de datos que proviene del respaldo o diseño en el caso del formulario. Reenviar a nuestro componente, por ejemplo, a una tarjeta de película:


const MovieCard = (props) => { const {title, genre, description, rating, image} = props.data; return ( <div> <img href={image} /> <div><h2>{title}</h2></div> <div><p>{genre}</p></div> <div><p>{description}</p></div> <div><h1>{rating}</h1></div> </div> ) } 

Para Ya sabemos sobre la expansión sin fin de los requisitos de los componentes. ¿De repente el título tendrá un enlace a la crítica de la película? ¿Y el género, para las mejores películas? No agregue ahora:


 const MovieCard = (props) => { const {title: {title}, description: {description}, rating: {rating}, genre: {genre}, image: {imageHref} } = props.data; return ( <div> <img href={imageHref} /> <div><h2>{name}</h2></div> <div><p>{genre}</p></div> <div><h1>{rating}</h1></div> <div><p>{description}</p></div> </div> ) } 

Por lo tanto, nos protegeremos de los problemas en el futuro, pero abriremos la puerta a un error de punto cero. Inicialmente, podríamos lanzar estructuras directamente desde atrás:


 <MovieCard data={res.data} /> 

Ahora, cada vez que necesite duplicar toda la información:


 <MovieCard data={{ title: {res.title}, description: {res.description}, rating: {res.rating}, image: {res.imageHref} }} /> 

Sin embargo, nos olvidamos del género, y el componente cayó. Y si no se establecieron los limitadores de error, entonces toda la aplicación está con él.


TypeScript viene al rescate. Simplificamos el esquema refactorizando la tarjeta y los elementos que la usan. Afortunadamente, todo se resalta en el editor o durante el ensamblaje:


 interface IMovieCardElement { text?: string; } interface IMovieCardImage { imageHref?: string; } interface IMovieCardProps { title: IMovieCardElement; description: IMovieCardElement; rating: IMovieCardElement; genre: IMovieCardElement; image: IMovieCardImage; } ... const {title: {text: title}, description: {text: description}, rating: {text: rating}, genre: {text: genre}, image: {imageHref} } = props.data; 

Para ahorrar tiempo, aún rodaremos los datos "como cualquiera" o "como IMovieCardProps". Que resulta Ya hemos descrito tres veces (si se usa en un lugar) una estructura de datos. Y que tenemos Un componente que aún no se puede modificar. Un componente que potencialmente podría bloquear toda la aplicación.


Es hora de reutilizar este componente. La calificación ya no es necesaria. Tenemos dos opciones:


Ponga utilería sin clasificar donde sea necesaria la calificación


 const MovieCard = ({withoutRating, ...props}) => { const {title: {title}, description: {description}, rating: {rating}, genre: {genre}, image: {imageHref} } = props.data; return ( <div> <img href={imageHref} /> <div><h2>{name}</h2></div> <div><p>{genre}</p></div> { withoutRating && <div><h1>{rating}</h1></div> } <div><p>{description}</p></div> </div> ) } 

Rápido, pero acumulamos accesorios y construimos una cuarta estructura de datos.


Hacer calificación en IMovieCardProps opcional. No olvides convertirlo en un objeto vacío por defecto


 const MovieCard = ({data, ...props}) => { const {title: {text: title}, description: {text: description}, rating: {text: rating} = {}, genre: {text: genre}, image: {imageHref} } = data; return ( <div> <img href={imageHref} /> <div><h2>{name}</h2></div> <div><p>{genre}</p></div> { data.rating && <div><h1>{rating}</h1></div> } <div><p>{description}</p></div> </div> ) } 

Más complicado, pero se hace difícil leer el código. Nuevamente, repita por cuarta vez . El control sobre el componente no es obvio, ya que está controlado opacamente por la estructura de datos. Digamos que se nos pidió que hiciéramos que la notoria calificación fuera un enlace, pero no en todas partes:


  rating: {text: rating, url: ratingUrl} = {}, ... { data.rating && data.rating.url ? <div>><h1><a href={ratingUrl}{rating}</a></h1></div> : <div><h1>{rating}</h1></div> } 

Y aquí nos topamos con la lógica compleja que dicta la estructura de datos opaca.


Patrón No. 2 - Componentes con su propio estado y reductores



Al mismo tiempo, un enfoque extraño y popular. Lo usé cuando comencé a trabajar con React y faltaba la funcionalidad JSX en Vue. Más de una vez escuché de desarrolladores en mitaps que este enfoque le permite omitir estructuras de datos más generalizadas:


  1. Un componente puede tomar muchas estructuras de datos; el diseño sigue siendo el mismo
  2. Al recibir datos, los procesa de acuerdo con el escenario deseado.
  3. Los datos se almacenan en el estado del componente para no iniciar el reductor con cada render (opcional)

Naturalmente, el problema de la opacidad (1) se complementa con el problema de la sobrecarga lógica (2) y la adición de estado al componente final (3).


El último (3) está dictado por la seguridad interna del objeto. Es decir, verificamos profundamente los objetos a través de lodash.isEqual. Si el evento es avanzado o JSON.stringify, todo acaba de comenzar. También puede agregar una marca de tiempo y verificar si todo está perdido. No es necesario guardar ni memorizar, porque la optimización puede ser más complicada que un reductor debido a la complejidad del cálculo.


Los datos se lanzan con el nombre del script (generalmente una cadena):


 <MovieCard data={{ type: 'withoutRating', data: res.data, }} /> 

Ahora escriba el componente:


 const MovieCard = ({data}) => { const card = reduceData(data.type, data.data); return ( <div> <img href={card.imageHref} /> <div><h2>{card.name}</h2></div> <div><p>{card.genre}</p></div> { card.withoutRating && <div><h1>{card.rating}</h1></div> } <div><p>{card.description}</p></div> </div> ) } 

Y la lógica:


 const reduceData = (type, data) = { switch (type) { case 'withoutRating': return { title: {data.title}, description: {data.description}, rating: {data.rating}, genre: {data.genre}, image: {data.imageHref} withoutRating: true, }; ... } }; 

Hay varios problemas en este paso:


  1. Al agregar una capa de lógica, finalmente perdemos la conexión directa entre los datos y la pantalla
  2. La duplicación de la lógica para cada caso significa que, en el caso de que todas las tarjetas necesiten una clasificación de edad, deberá registrarse en cada reductor
  3. Otros problemas restantes del paso 1

Patrón n. ° 3: Transferencia de datos y lógica de visualización a la gestión de estado



Aquí abandonamos el bus de datos para construir interfaces, que es React. Utilizamos tecnología con nuestro propio modelo lógico. Esta es probablemente la forma más común de crear aplicaciones en React, aunque la guía le advierte que no use el contexto de esta manera.


Utilice herramientas similares donde React no proporcione herramientas suficientes, por ejemplo, en el enrutamiento. Lo más probable es que esté utilizando react-router. En este caso, usar el contexto en lugar de reenviar la devolución de llamada desde el componente de cada ruta de nivel superior tendría más sentido reenviar la sesión a todas las páginas. React no tiene una abstracción separada para acciones asincrónicas distintas de las que ofrece el lenguaje Javascript.


Parece que hay una ventaja: podemos reutilizar la lógica en futuras versiones de la aplicación. Pero esto es un engaño. Por un lado, está vinculado a la API, por otro, a la estructura de la aplicación, y la lógica proporciona esta conexión. Al cambiar una de las partes, debe reescribirse.


Solución: Patrón No. 4 - composición



El método de composición es obvio si sigue los siguientes principios (aparte de un enfoque similar en Patrones de diseño ):


  1. Desarrollo frontend - desarrollo de interfaces de usuario - usa HTML para el diseño
  2. Javascript se utiliza para recibir, transmitir y procesar datos.

Por lo tanto, transfiera datos de un dominio a otro lo antes posible. React utiliza la abstracción JSX para la plantilla HTML, pero de hecho utiliza un conjunto de métodos createElement. Es decir, los componentes JSX y React, que también son elementos JSX, deben tratarse como un método de visualización y comportamiento, en lugar de la transformación y el procesamiento de datos que deben ocurrir en un nivel separado.


En este paso, muchos usan los métodos enumerados anteriormente, pero no resuelven el problema clave de expandir y modificar la visualización de los componentes. En la documentación se muestra cómo hacerlo, según los creadores de la biblioteca:


 function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); } function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); } 

Es decir, como parámetros, en lugar de cadenas, números y tipos booleanos, se transfieren los componentes confeccionados.


Por desgracia, este método también resultó ser inflexible. He aquí por qué:


  1. Se requieren ambos accesorios. Esto limita la reutilización de componentes.
  2. Opcional significaría sobrecargar el componente SplitPane con lógica.
  3. La anidación y la pluralidad no se muestran semánticamente.
  4. Esta lógica de mapeo debería reescribirse para cada componente que acepte accesorios.

Como resultado, dicha solución puede crecer en complejidad incluso para escenarios bastante simples:


 function SplitPane(props) { return ( <div className="SplitPane"> { props.left && <div className="SplitPane-left"> {props.left} </div> } { props.right && <div className="SplitPane-right"> {props.right} </div> } </div> ); } function App() { return ( <SplitPane left={ contacts.map(el => <Contacts name={ <ContactsName name={el.name} /> } phone={ <ContactsPhone name={el.phone} /> } /> ) } right={ <Chat /> } /> ); } 

En la documentación, un código similar, en el caso de componentes de orden superior (HOC) y render-props, se llama un " infierno de envoltura". Con cada adición de un nuevo elemento, la legibilidad del código se vuelve más difícil.


Una respuesta a este problema, las ranuras, está presente en la tecnología de componentes web y en el marco Vue . Sin embargo, en ambos lugares hay restricciones: en primer lugar, las ranuras se definen no por un símbolo, sino por una cadena, lo que complica la refactorización. En segundo lugar, las ranuras tienen una funcionalidad limitada y no pueden controlar su propia pantalla, transferir otras ranuras a componentes secundarios o reutilizarse en otros elementos.


En resumen, algo así, llamémoslo patrón No. 5 - ranuras :


 function App() { return ( <SplitPane> <LeftPane> <Contacts /> </LeftPane> <RightPane> <Chat /> </RightPane> </SplitPane> ); } 

Hablaré de esto en el próximo artículo sobre las soluciones existentes para el patrón de ranuras en React y sobre mi propia solución.

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


All Articles