Patterns Avançados de Composição
Compound Components, Render Props, Controlled/Uncontrolled. Patterns de biblioteca nível senior.
Definição
Patterns de composição avançados permitem criar APIs de componentes flexíveis e reutilizáveis. Compound Components: múltiplos componentes que compartilham estado implícito via Context (como `<Select><Option>` do HTML). Render Props: componente passa dados para filhos via função. Controlled/Uncontrolled: componente aceita valor externo ou gerencia internamente. Headless Components: lógica sem UI (Radix, Headless UI).
Problema → Solução
Problema
Componentes com prop drilling excessivo, ou com 15 props booleanas para customização. APIs rígidas que não se adaptam a casos de uso ligeiramente diferentes.Solução
Compound Components para componentes com múltiplas partes relacionadas. Controlled pattern para dar ao pai controle sobre o estado. Default uncontrolled com possibilidade de controle externo (como inputs HTML). Context interno para comunicação entre sub-componentes.Dica de entrevista
Tip
Headless UI libraries (Radix, Headless UI, React Aria) são o padrão moderno: toda a lógica e acessibilidade sem estilos. Você traz seus estilos. Isso é o 'headless pattern' levado ao extremo. Para componentes próprios: prefira Compound Components sobre prop drilling quando há mais de 2-3 partes relacionadas.Código
// Compound Components com Context implícito
const TabsContext = createContext<TabsContextValue | null>(null)
function Tabs({ children, defaultValue }: TabsProps) {
const [active, setActive] = useState(defaultValue)
return (
<TabsContext.Provider value={{ active, setActive }}>
<div role="tablist">{children}</div>
</TabsContext.Provider>
)
}
Tabs.Tab = function Tab({ value, children }: TabProps) {
const { active, setActive } = useContext(TabsContext)!
return (
<button
role="tab"
aria-selected={active === value}
onClick={() => setActive(value)}
>
{children}
</button>
)
}
Tabs.Panel = function Panel({ value, children }: PanelProps) {
const { active } = useContext(TabsContext)!
return active === value ? <div role="tabpanel">{children}</div> : null
}
// Uso — API natural
<Tabs defaultValue="overview">
<Tabs.Tab value="overview">Visão Geral</Tabs.Tab>
<Tabs.Tab value="details">Detalhes</Tabs.Tab>
<Tabs.Panel value="overview"><Overview /></Tabs.Panel>
<Tabs.Panel value="details"><Details /></Tabs.Panel>
</Tabs>
// Controlled/Uncontrolled pattern
function Toggle({ value, defaultValue = false, onChange }: ToggleProps) {
const [internal, setInternal] = useState(defaultValue)
const isControlled = value !== undefined
const checked = isControlled ? value : internal
function handleChange() {
if (!isControlled) setInternal(v => !v)
onChange?.(!checked)
}
return <button onClick={handleChange}>{checked ? 'ON' : 'OFF'}</button>
}Perguntas de entrevista
Q.O que são Compound Components?▾
A.Grupo de componentes que trabalham juntos com estado compartilhado implícito via Context. Exemplo: `<Tabs>`, `<Tabs.List>`, `<Tabs.Tab>`, `<Tabs.Panel>`. O Tabs gerencia qual tab está ativa, os filhos consomem via Context sem precisar de props. API mais natural para o consumidor — parece HTML nativo.
Q.Controlled vs Uncontrolled — qual padrão usar?▾
A.Uncontrolled por default (mais fácil de usar), controlled quando necessário (máximo controle). O padrão 'semi-controlled' aceita value + onChange (controlled) OU defaultValue (uncontrolled). Internamente usa useState com valor inicial de defaultValue. Se value prop aparece, o componente passa a ser controlled. Exatamente como inputs HTML.
Q.Quando Render Props ainda é relevante?▾
A.Com hooks, muitos casos de render props foram substituídos por custom hooks. Mas render props ainda têm vantagem quando o componente precisa controlar O QUE renderiza (não só fornecer lógica). Ex: componente de virtualização que controla quais itens são visíveis. React Query usava render props, migrou para hooks — mas `children as function` ainda aparece em componentes de UI complexos.
Termos relacionados
83/122← →