— React, Typescript, Frontend, Desing pattern — 3 min read
Lo sviluppo di applicazioni frontend negli ultimi anni si è indirizzato sempre più verso verso un approccio basato sui componenti.
I componenti permettono di suddividere l'inferfaccia utente in parti indipendenti e riutilizzabili.
Con react è molto facile definire un componente: basta definire una funzione. Prendiamo come esempio la creazione di un componente calendario. All'inizio la definizione è la seguente:
1const Calendar = (data) => (2 <div>3 {*visualizza i giorni del mese indicato dalla data *}4 <div>5)
Questo componente possiamo utizzarlo all'interno di una applicazione minimale semplicemente così:
1const App = () => (2 <div>3 <Calendar data={} />4 </div>5);
Nello sviluppo del software purtroppo le cose non rimangono mai (😀) così semplici e man mano i requisiti e le richieste di nuove funzionalità crescono.
Dopo poco tempo, per esempio, ci viene chiesto di inserire un titolo nel calendario: una soluzione potrebbe essere quella di creare un nuovo componente calendario con un titolo. Ma quello che vogliamo ottenere è il riutilizzo del nostro componente.
Uno dei principi più importanti dell'ingegneria del software è il principio DRY: don't repeat yourself. La duplicazione del codice porta molti problemi, il più evidente è che se una parte del codice duplicato viene modificata, anche tutte le altre parti duplicate devono essere aggiornate e alla lunga il sistema diventa sempre più difficile da gestire. Questo principio viene da lontano: è stato introdotto nel 1999 in un libro che è poi diventato un testo di riferimento per la progettazione del software: The Pragmatic Programmer.
Torniamo al nostro componente calendario: vogliamo aggiungere il titolo ed ottenere un componente riutilizzabile. Il modo più semplice è quello di creare una proprietà titolo nel componente.
All'inizio con qualche proprietà aggiuntiva possiamo risolvere tutti i nostri problemi di riutilizzo, ma nel tempo le richieste di solito aumentano, nel nostro caso ad esempio ci chiedono di aggiungere le seguenti funzionalità:
Se ad ogni requisito aggiungiamo al componente una o più proprietà ad un certo punto il componente diventerà ingestibile:
1<CalendarMonth2 title="Data acquisto"3 showLeftNavigation={true}4 showRightNavigation={true}5 selectDate={date}6 showBottom={true}7 showNavigation={false}8 showMonths={true}9 ...10 ...11/>
Un approccio migliore alla creazione di componenti flessibili e riutilizzabili è quello di utilizzare i compound components.
In pratica si tratta di scomporre il componente calendario in tanti piccoli componenti e di utilizzare il context di react per far comunicare in modo implicito tutti questi piccoli "mattoncini".
In questo modo possiamo descrivere in modo dichiarativo il componente calendario e possiamo inserire o togliere funzionalità in modo molto semplice.
1<CalendarMonth locale={'en'}>2 <CalendarMonthTitle />3 <CalendarMonthWeekdays />4 <CalendarMonthDays />5</CalendarMonth>
Creiamo un context per far comunicare tutti questi componenti:
1const CalendarMonthContext = React.createContext<CalendarContext | undefined>(undefined);
Il componente padre fornirà il provider di questo context a tutti i componenti figli:
1return (2 <CalendarMonthContext.Provider value={valori_da_condividere}>3 <div>{children}</div>4 </CalendarMonthContext.Provider>5);
Per utilizzare questo context il componente figlio deve trovarsi come child del componente padre. Per verificare se è effettivante così possiamo creare la seguente funzione di controllo:
1export function useCalendarMonth() {2 const context = React.useContext(CalendarMonthContext);3 if (context === undefined) {4 throw new Error('useCalendar must be used within a CalendarMonthProvider');5 }6 return context;7}
A questo punto, in ogni figlio con useCalendarMonth
possiamo utilizzare i valori presenti nel context.
Vediamo l'implementazione del componente Today
, che dopo aver navigato tra i vari mesi,
ci permette di tornare alla data selezionata:
1import { useCalendarMonth } from './CalendarMonth';2import css from './CalendarMonthTitle.module.css';34export const CalendarMonthToday = () => {5 const { dispatch } = useCalendarMonth();6 const date = new Date(Date.now());7 return (8 <button className={css.changeToday} onClick={() => dispatch({ type: 'CHANGE_DAY', payload: date })}>9 Today10 </button>11 );12};
Come possiamo vedere qua sopra l'implementazione di questo componente è molto semplice. Tutta la logica di gestione delle date e dei valori che sono gestiti dal calendario si trova in un reducer che è passato alla context API.
In questo modo i componenti figli possono cambiare lo stato del componente eseguendo il dispatch
di azioni.
Come creare, utilizzare ed estendere un reducer lo vedremo in un prossimo articolo.
Torniamo al componente calendario.
L'utilizzo del design pattern dei componenti composti, permette la creazione di componenti che possono essere utilizzati e personalizzati in modo molto semplice dagli sviluppatori (il loro uso è semplice, la loro creazione un po' meno 🤣).
Alla richiesta di un nuovo requisito si potrà operare personalizzando un singolo figlio o creando un nuovo componente figlio che andrà ad operare con gli altri children.
L'approccio dichiarativo di react poi rende evidente in modo visivo le personalizzazioni richieste. Se ad esempio vogliamo aggiungere i mesi dell'anno possiamo creare un nuovo figlio e poi aggiungerlo nel padre, vediamo come:
1import css from './CalendarMonth.module.css';2import { useCalendarMonth } from './CalendarMonth';34export const CalendarMonthMonthsList = () => {5 const { state, dispatch } = useCalendarMonth();6 return (7 <ul className={css.calendarMonths}>8 {state.monthNamesShort.map((month, index) => (9 <li10 role="button"11 className={css.monthShort}12 key={month.toString()}13 onClick={() => dispatch({ type: 'CHANGE_MONTH', payload: index })}14 >15 {month}16 </li>17 ))}18 </ul>19 );20};
1<CalendarMonth locale={'it'}>2 <CalendarMonthTitle>3 <CalendarMonthToday />4 </CalendarMonthTitle>5 <CalendarMonthMonthsList /> {* <- aggiunto qui *}6 <CalendarMonthWeekdays />7 <CalendarMonthDays />8</CalendarMonth>
Il risultato sarà questo:
Per la gestione delle date e dell'internalizzazione del componente ho utilizzato la libreria dayjs. Rispetto alla più storica moment, dayjs è molto più piccola. Il core della libreria è di pochi kb. Poi possiamo integrare le funzionalità che ci servono utilizzando tanti piccoli plugin.
Il codice sorgende del calendario è visibile qui:
https://rzaniboni.github.io/demo-react-calendar/
React Hooks: Compound Components - dal blog di Kent C. Dodds