Un nuovo approccio per reagire alla progettazione dei componenti

Nel 2015, Dan Abramov ha scritto un articolo, Presentational and Container Components, che alcuni nuovi arrivati ​​di React hanno frainteso come comandamenti. In effetti, io stesso sono incappato nell'articolo e in molti altri che riecheggiavano i suoi insegnamenti e ho pensato, questo deve essere il modo migliore per separare le preoccupazioni tra i componenti .

Ma lo stesso Dan Abramov in seguito si rivolse alla comunità per essersi aggrappato ai modelli di design che delineava.

Lavorando con React da più di un anno, sono incappato nei miei modelli di design e qui cercherò di formalizzarli. Prendi queste idee con le pinze, sono solo mie osservazioni che ho trovato costruttive.

Sfuggire alla dicotomia

Per molto tempo, i componenti sono stati ampiamente classificati come intelligenti o stupidi, contenitori o di presentazione, stati o apolidi, puri o impuri. C'è molta terminologia, ma significano tutti la stessa cosa. I componenti intelligenti sanno come legare insieme la tua applicazione e componenti stupidi prendono semplicemente i dati per presentarli all'utente finale. Questa è una distinzione utile, ma in realtà non è il modo in cui mi trovo a pensare durante la progettazione dei componenti.

Il problema con la mentalità Contenitore vs Presentazione è che si sforza di definire le responsabilità dei componenti in termini di stato, logica e altri aspetti del funzionamento interno di un componente.

La progettazione dei componenti viene affrontata meglio rimandando i dettagli di implementazione e pensando in termini di interfacce dei componenti . È particolarmente importante pensare al tipo di personalizzazioni che un componente dovrebbe consentire e al tipo di dipendenze implicite ed esplicite che un componente dovrebbe includere.

Presentazione della tricotomia

Tricotomia? È anche solo una parola? Non lo so, ma hai capito. Sono arrivato a pensare che i componenti di React cadano in uno dei tre contenitori.

Componenti universali

Questi sono componenti che possono essere utilizzati molte volte in qualsiasi applicazione .

Questi componenti:

  • Dovrebbe essere riutilizzabile
  • Dovrebbe essere altamente personalizzabile
  • Non dovrebbe essere a conoscenza del codice specifico dell'applicazione, inclusi modelli, negozi, servizi, ecc.
  • Dovrebbe ridurre al minimo le dipendenze da librerie di terze parti
  • Dovrebbe essere usato raramente direttamente nella tua applicazione
  • Dovrebbe essere utilizzato come elementi costitutivi per i componenti globali
  • Può terminare con il suffisso "Base" (ad es. ButtonBase, ImageBase)

Si tratta di componenti fondamentali che sono indipendenti dall'applicazione e non devono essere necessariamente utilizzati direttamente nei componenti di visualizzazione perché spesso sono troppo personalizzabili. Utilizzarli direttamente nella tua Vista componenti significherebbe copiare e incollare molto la stessa piastra della caldaia. Rischi inoltre che gli sviluppatori abusino della natura altamente personalizzabile dei componenti in modi che creano un'esperienza incoerente nella tua applicazione.

Componenti globali

Questi sono componenti che possono essere utilizzati molte volte in una sola applicazione .

Questi componenti:

  • Dovrebbe essere riutilizzabile
  • Dovrebbe essere minimamente personalizzabile
  • Può utilizzare codice specifico dell'applicazione
  • Dovrebbe implementare componenti universali , limitandone la personalizzazione
  • Dovrebbe essere usato come blocchi di costruzione per i componenti di visualizzazione
  • Spesso collega uno a uno con le istanze del modello (ad es. DogListItem, CatCard)

Questi componenti sono riutilizzabili all'interno dell'applicazione ma non sono facilmente trasferibili ad altre applicazioni perché dipendono dalla logica dell'applicazione. Questi sono gli elementi costitutivi dei componenti di visualizzazione e di altri componenti globali.

Dovrebbero essere personalizzabili al minimo per garantire la coerenza in tutta l'applicazione. Le applicazioni non dovrebbero avere trenta diverse varianti di pulsanti, ma piuttosto dovrebbero avere una manciata di diverse varianti di pulsanti. Questo dovrebbe essere applicato prendendo un componente ButtonBase universale altamente personalizzabile e incorporandovi stili e funzionalità sotto forma di un componente Button globale. I componenti globali spesso assumono un'altra forma come rappresentazioni dei dati del modello di dominio.

Visualizza componenti

Si tratta di componenti che vengono utilizzati una sola volta nell'applicazione .

Questi componenti:

  • Non dovrebbe essere preoccupato per la riutilizzabilità
  • È probabile che gestiscano lo stato
  • Ricevi oggetti di scena minimi
  • Dovrebbe legare insieme componenti globali (e possibilmente componenti universali)
  • Spesso risolve i percorsi delle applicazioni
  • Mantenere spesso un appezzamento dedicato di proprietà del viewport
  • Hanno spesso un numero elevato di dipendenze
  • Dovrebbero essere i mattoni della tua applicazione

Questi sono i componenti di livello più alto della tua applicazione che uniscono componenti riutilizzabili e anche altre viste. Questi saranno spesso i componenti che risolvono i percorsi e possono essere visualizzati sotto forma di componenti a livello di pagina. Sono pesanti nello stato e leggeri negli oggetti di scena. Questi sono ciò che Dan Abramov considererebbe i componenti dei container.

Il PromiseButton

Diamo un'occhiata alle implementazioni universale e globale di un pulsante di promessa e vediamo come si confrontano. Un pulsante di promessa si comporta come un normale pulsante a meno che il gestore onClick non restituisca una promessa. Nel caso di una promessa restituita, il pulsante può rendere condizionatamente il contenuto in base allo stato della promessa.

Si noti come PromiseButtonBase ci consenta di controllare cosa rendere in qualsiasi momento del ciclo di vita della promessa, ma PromiseButton si cuoce nel PulseLoader verde acqua durante lo stato di attesa. Ora, ogni volta che utilizziamo PromiseButton, ci viene garantita un'animazione di caricamento verde acqua e non dobbiamo preoccuparci di duplicare quel codice o fornire un'esperienza di caricamento incoerente includendo più animazioni di caricamento di più colori nella nostra applicazione. PromiseButtonBase è personalizzabile, ma PromiseButton è restrittivo.

Struttura delle directory

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.