R
Interview Deckreact · next.js · senior/staff
0·0·122
Avançado

Patterns Avançados de Composição

Compound Components, Render Props, Controlled/Uncontrolled. Patterns de biblioteca nível senior.

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
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.
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.
// 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>
}
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.
83/122← →