Un nuevo enfoque para reaccionar el diseño de componentes

En 2015, Dan Abramov escribió un artículo, Presentational and Container Components, que algunos recién llegados a React malinterpretaron como mandamientos. De hecho, yo mismo me topé con el artículo y con muchos otros que hacían eco de sus enseñanzas y pensé que esta debe ser la mejor manera de separar las preocupaciones entre los componentes .

Pero, el propio Dan Abramov se dirigió más tarde a la comunidad por aferrarse a los patrones de diseño que describió.

Trabajando con React durante más de un año, me he encontrado con mis propios patrones de diseño y aquí intentaré formalizarlos. Tome estas ideas con un grano de sal, son solo mis propias observaciones que he encontrado constructivas.

Escapar de la dicotomía

Durante mucho tiempo, los componentes se han clasificado ampliamente como inteligentes o tontos, contenedores o presentacionales, con estado o sin estado, puros o impuros. Hay mucha terminología, pero todos significan lo mismo. Los componentes inteligentes saben cómo unir su aplicación y los componentes tontos solo toman datos para presentarlos al usuario final. Esta es una distinción útil, pero en realidad no es lo que pienso al diseñar componentes.

El problema con la mentalidad de contenedor frente a presentacional es que se esfuerza demasiado en definir las responsabilidades de los componentes en términos de estado, lógica y otros aspectos del funcionamiento interno de un componente.

El diseño de componentes se aborda mejor si se difieren los detalles de implementación y se piensa en términos de interfaces de componentes . Es particularmente importante pensar qué tipo de personalizaciones debería permitir un componente y qué tipo de dependencias implícitas y explícitas debería incluir un componente.

Presentando la tricotomía

¿Tricotomía? ¿Es eso siquiera una palabra? No lo sé, pero entiendes la idea. He llegado a pensar que los componentes de React caen en uno de los tres contenedores.

Componentes universales

Estos son componentes que se pueden utilizar muchas veces en cualquier aplicación .

Estos componentes:

  • Debe ser reutilizable
  • Debe ser altamente personalizable
  • En caso de no estar al tanto de código de la aplicación específica incluyendo modelos, tiendas, servicios, etc.
  • Debería minimizar las dependencias de bibliotecas de terceros
  • Rara vez debe usarse directamente en su aplicación
  • Deben usarse como bloques de construcción para componentes globales
  • Puede terminar con el sufijo "Base" (por ejemplo, ButtonBase, ImageBase)

Estos son componentes fundamentales que son independientes de la aplicación y no necesariamente deben usarse directamente en sus componentes de View porque a menudo son demasiado personalizables. Usarlos directamente en sus componentes View significaría mucho copiar y pegar la misma placa de caldera. También corre el riesgo de que los desarrolladores abusen de la naturaleza altamente personalizable de los componentes de manera que creen una experiencia incoherente en toda su aplicación.

Componentes globales

Estos son componentes que se pueden utilizar muchas veces en una aplicación .

Estos componentes:

  • Debe ser reutilizable
  • Debe ser mínimamente personalizable
  • Puede usar código específico de la aplicación
  • Debería implementar componentes universales , restringiendo su personalización
  • Deben usarse como bloques de construcción para los componentes de View
  • A menudo se vincula uno a uno con instancias de modelo (por ejemplo, DogListItem, CatCard)

Estos componentes son reutilizables dentro de su aplicación, pero no se transfieren fácilmente a otras aplicaciones porque dependen de la lógica de la aplicación. Estos son los bloques de construcción para los componentes de View y otros componentes globales.

Deben ser mínimamente personalizables para garantizar la coherencia en toda su aplicación. Las aplicaciones no deben tener treinta variaciones de botones diferentes, sino que deben tener un puñado de variaciones de botones diferentes. Esto se debe hacer cumplir tomando un componente de Universal ButtonBase altamente personalizable e integrando estilos y funcionalidades en la forma de un componente de Botón Global. Los componentes globales a menudo toman otra forma como representaciones de datos del modelo de dominio.

Ver componentes

Estos son componentes que se utilizan solo una vez en su aplicación .

Estos componentes:

  • En caso de no estar preocupado por la reutilización
  • Es probable que administren el estado
  • Recibe accesorios mínimos
  • Debería unir componentes globales (y posiblemente componentes universales)
  • A menudo resuelve rutas de aplicaciones
  • Mantener a menudo una parcela dedicada de bienes raíces con vistas
  • Suelen tener un gran número de dependencias
  • Deben ser bloques de construcción para su aplicación

Estos son los componentes de más alto nivel de su aplicación que unen componentes reutilizables e incluso otras vistas. A menudo, estos serán los componentes que resuelven las rutas y pueden mostrarse en forma de componentes a nivel de página. Son pesados ​​en estado y ligeros en accesorios. Estos son los que Dan Abramov consideraría componentes de contenedores.

El botón de promesa

Echemos un vistazo a las implementaciones universales y globales de un botón de promesa y veamos cómo se comparan. Un botón de promesa actúa como un botón ordinario a menos que el controlador onClick devuelva una promesa. En el caso de una promesa devuelta, el botón puede representar contenido condicionalmente según el estado de la promesa.

Observe cómo PromiseButtonBase nos permite controlar qué renderizar en cualquier punto del ciclo de vida de la promesa, pero PromiseButton se hornea en el PulseLoader verde azulado durante el estado pendiente. Ahora, cada vez que usamos PromiseButton, tenemos garantizada una animación de carga verde azulado y no tenemos que preocuparnos por duplicar ese código o proporcionar una experiencia de carga inconsistente al incluir múltiples animaciones de carga de varios colores en nuestra aplicación. PromiseButtonBase es personalizable, pero PromiseButton es restrictivo.

Estructura de directorios

The following illustrates how we might organize components following this pattern.

App/ App.js Views/ DogListView/ Global/ Models/ Dog/ DogListItem/ Image/ PromiseButton/ Universal/ ImageBase/ PromiseButtonBase/

Component Dependencies

Below illustrates how the above components depend on one another.

/* App.js */ import { DogListView } from './Views' /* DogListView.js */ import { DogListItem } from 'App/Global/Models/Dog' /* DogListItem.js */ import Image from '../../Image', import PromiseButton from '../../PromiseButton' /* Image.js */ import { ImageBase } from 'Universal' /* PromiseButton.js */ import { PromiseButtonBase } from 'Universal'

Our View component depends on a Global component and our Global components depend on other Global components as well as Universal components. This dependency flow will be pretty common. Notice also the use of absolute and relative imports. It’s nice to use relative imports when pulling in dependencies that reside within the same module. Also, it’s nice to use absolute imports when pulling in dependencies across modules or when your directory structure is deeply nested or frequently changing.

The problem with the Container vs Presentational model is that it tries too hard to define component responsibilities in terms of component inner-workings. The key takeaway is to view component design in terms of component interfaces. What matters less is the implementation that allows the component to satisfy its contract. It’s important to think about what kind of customizations a component should allow and what kind of implicit and explicit dependencies a component should include.

If you’ve found these thoughts helpful and would like to see more of my ideas, feel free to check out this repo which I use to maintain my thoughts and best practices for writing React/Redux apps.