
All'interno dei nostri applicativi usiamo spesso il pattern dei compound components per costruire widget ricchi mantenendo una API compositiva e leggibile. In questo articolo raccolgo criteri, esempi e buone pratiche adottate nel progetto per chi deve scrivere o estendere componenti complessi senza sacrificare la developer experience.
Perche' scegliere i compound components
Anatomia del pattern
1// apps/app/components/QuoteMobileCard.tsx2const QuoteMobileCardContext = createContext<QuoteMobileCardContextProps | undefined>(undefined);34const useQuoteMobileCardContext = () => {5 const context = useContext(QuoteMobileCardContext);67 if (!context) {8 throw new Error('QuoteMobileCard compound components must be used within a QuoteMobileCard provider');9 }1011 return context;12};
1// apps/app/components/QuoteMobileCard.tsx2function QuoteMobileCardRoot({3 children,4 isLoading = false,5 onClick,6}: QuoteMobileCardRootProps) {7 return (8 <QuoteMobileCardContext.Provider value={{ isLoading }}>9 <Stack10 component={ButtonBase}11 onClick={onClick}12 >13 {children}14 </Stack>15 </QuoteMobileCardContext.Provider>16 );17}
1// apps/app/components/QuoteMobileCard.tsx2const QuoteMobileCardTitle = ({ children }: { readonly children: React.ReactNode }) => {3 const { isLoading } = useQuoteMobileCardContext();4 return isLoading5 ? <Skeleton variant="text" width="40%" />6 : <Typography variant="h6">{children}</Typography>;7};
1// apps/app/components/QuoteMobileCard.tsx2export const QuoteMobileCard = {3 Root: QuoteMobileCardRoot,4 Main: QuoteMobileCardMain,5 Title: QuoteMobileCardTitle,6 Subtitle: QuoteMobileCardSubtitle,7};
Qui sotto trovi un esempio pratico di composizione opzionale costruito sugli stessi tasselli visti sopra. Ogni blocco puo' essere inserito o omesso in base alla view corrente, mantenendo l'API dichiarativa.
1<QuoteMobileCard.Root isLoading={isLoading}>2 <QuoteMobileCard.Title>3 {quoteTitle}4 </QuoteMobileCard.Title>56 {showSubtitle && (7 <QuoteMobileCard.Subtitle>8 Aggiornato il {lastUpdate}9 </QuoteMobileCard.Subtitle>10 )}1112 <QuoteMobileCard.Main13 name={storeCode}14 infoString={description}15 />16</QuoteMobileCard.Root>
Per introdurre il tassello opzionale QuoteMobileCard.Subtitle basta definire un nuovo sotto-componente che legge il medesimo context e gestisce il fallback in fase di loading.
1const QuoteMobileCardTitle = ({ children }: { readonly children: React.ReactNode }) => {2 const { isLoading } = useQuoteMobileCardContext();3 return isLoading4 ? <Skeleton variant="text" width="40%" sx={{ fontSize: '1rem' }} />5 : <Typography variant="h6">{children}</Typography>;6};78const QuoteMobileCardSubtitle = ({ children }: { readonly children: React.ReactNode }) => {9 const { isLoading } = useQuoteMobileCardContext();10 return isLoading11 ? <Skeleton variant="text" width="60%" sx={{ fontSize: '0.75rem' }} />12 : <Typography variant="body2" color="text.secondary">{children}</Typography>;13};1415export const QuoteMobileCard = {16 Root: QuoteMobileCardRoot,17 Main: QuoteMobileCardMain,18 Title: QuoteMobileCardTitle,19 Subtitle: QuoteMobileCardSubtitle,20};
In questo modo chi utilizza il componente decide quali blocchi montare, senza propagare prop aggiuntive o scrivere condizionali sparsi nel layout.
Buone pratiche che manteniamo
Come costruire un nuovo compound component
Il pattern dei compound components ci permette di mantenere i layout complessi coerenti in tutta l'applicazione, riducendo prop drilling e migliorando la leggibilita'. Seguendo gli step descritti sopra possiamo introdurre nuovi widget composti garantendo la stessa DX.