Skip to content
This page has been auto-translated and may contain errors.View in English

Props de renderização e componentes sem interface

Todo componente até agora teve uma aparência: renderiza algo que você vê. Um componente sem interface não tem uma UI estilizada própria. Ele existe puramente para fornecer comportamento, e renderiza o que quer que você passe como filhos. Isso soa abstrato até você perceber quantos widgets são o mesmo comportamento com roupas diferentes: uma estrela favorita, um menu suspenso, um interruptor de modo escuro e uma seção "mostrar mais" são todos um booleano e uma função de alternância. Um componente Toggle sem interface captura essa lógica uma única vez, e cada widget se torna marcação composta ao seu redor.

Este capítulo segue o curso construindo esse Toggle como uma família de componentes compostos coordenada por context, depois bate na parede que leva à segunda ideia do capítulo: props de renderização, o padrão para expor o estado interno de um componente para quem o renderiza.

Uma família Toggle sem interface

Toggle possui um booleano e o fornece, junto com uma função toggle, por um provider ToggleContext com escopo. O resto da família consome esse context, e cada peça fica pendurada em Toggle como uma propriedade (Toggle.Button = ToggleButton), a sintaxe de ponto dos componentes compostos:

jsx
<Toggle>
  <Toggle.Button>
    <Star />
  </Toggle.Button>
  <Toggle.On>O toggle está ligado</Toggle.On>
  <Toggle.Off>O toggle está desligado</Toggle.Off>
</Toggle>
  • Toggle.Button renderiza seus filhos em um <button type="button"> com um onClick que inverte o estado. Os filhos não precisam de seus próprios manipuladores de clique; um clique em qualquer coisa dentro propaga para cima até o wrapper, que é event bubbling fazendo o trabalho, através do sistema de eventos sintéticos do React em vez de um listener no próprio button. Uma <div> clicável se comportaria da mesma forma para usuários de mouse, mas perderia a ativação por teclado e o anúncio do leitor de tela, como acessibilidade cobre.
  • Toggle.On renderiza seus filhos quando o estado é verdadeiro, e null caso contrário. Toggle.Off faz o inverso.

Os ícones de estrela, a marcação do menu, os rótulos, tudo vem de quem chama. É isso que torna o componente reutilizável em widgets que parecem completamente diferentes: o curso coloca o mesmo Toggle sob a estrela e o menu, e as próprias peças do menu envolvem as peças do Toggle internamente para que a pessoa usando <Menu> nunca veja a maquinaria.

Deixando o exterior ouvir

Um botão de estrela real não apenas se repinta; ele diz a um servidor que alguém clicou nele. Então o componente sem interface precisa de sua própria prop listener de evento, a mesma forma que onClick nos seus próprios componentes do capítulo filhos e composição. Toggle aceita uma função onToggle e a executa de um efeito sempre que o estado muda:

jsx
function Toggle({ children, onToggle = () => {} }) {
  const [on, setOn] = useState(false)
  const toggle = () => setOn(prevOn => !prevOn)
  const firstRender = useRef(true)

  useEffect(() => {
    if (firstRender.current) {
      firstRender.current = false
    } else {
      onToggle()
    }
  }, [on])

  // o provider e os filhos renderizam abaixo
}

Duas pequenas peças de técnica estão escondidas ali. A ref protege contra a primeira execução do efeito: efeitos disparam após o render inicial também, e anunciar uma "alternância" que ninguém realizou é um bug, então a ref controla se ainda é o primeiro render sem disparar re-renders da forma que o estado faria. E o padrão = () => {} é uma função noop, então quem chama e não se importa com o evento não quebra o componente quando ele chama onToggle() mesmo assim.

O array de dependências contém apenas on. Adicionar onToggle, que é o que exhaustive-deps pede, re-executaria o efeito toda vez que quem chama passa uma função inline nova, então este é um dos lugares onde a regra lint e a intenção discordam. Uma nuança na proteção também aparece em desenvolvimento. StrictMode executa cada efeito duas vezes no mount, que gasta a ref na primeira execução e deixa o callback disparar na segunda. Builds de produção não fazem isso, e o fix duradouro é derivar o comportamento da mudança em si, comparando on contra o valor anterior, em vez de se a primeira renderização.

Props de renderização: levando o interior para fora

Agora o padrão bate na parede. Estilize uma caixa com uma transição CSS na cor de fundo, renderize a versão preenchida dentro de Toggle.On e a vazia dentro de Toggle.Off, e a transição nunca toca. O motivo: Toggle.On e Toggle.Off montam e desmontam seus filhos. React não está mudando uma classe em um elemento; está removendo um elemento e inserindo um diferente, e CSS não pode fazer transição de um elemento que deixou de existir. O que quem chama precisa é do próprio estado on, para que um elemento persistente possa variar seu próprio class name. O estado está preso dentro de Toggle.

A válvula de escape é um movimento familiar do JavaScript: callbacks invertem controle. Quando você chama addEventListener("click", callback), você fornece a função, mas o navegador a chama e o navegador decide o que ela recebe (o objeto de evento). Um componente pode oferecer o mesmo acordo. Passe uma função, e o componente chamará essa função com seu estado interno e renderizará o que a função retorna:

jsx
function ToggleDisplay({ children }) {
  const { on } = useContext(ToggleContext)
  return children(on)
}

Toggle.Display = ToggleDisplay
jsx
<Toggle.Display>
  {on => <div className={`box ${on ? 'filled' : ''}`} />}
</Toggle.Display>

Os filhos de Toggle.Display não são elementos desta vez; são uma função. Toggle.Display a chama, passa on, e retorna o JSX que recebe de volta. Agora a div é um elemento que sobrevive a cada alternância, apenas sua classe muda, e a transição toca. Este é o padrão render props: uma prop cujo valor é uma função que o componente chama para saber o que renderizar. Algumas APIs usaram uma prop literal chamada render, que é de onde vem o nome do padrão; passar a função como children é a forma que pegou.

Render props resolvem compartilhamento de estado entre um componente e quem o chama; custom hooks resolvem compartilhamento de estado entre funções simples, e depois que hooks chegaram o ecossistema moveu a maioria das APIs de render-prop. Um hook useToggle() devolve on e toggle sem componente extra, sem nesting na árvore, e sem indireção de função-como-filhos, que é por que bibliotecas que costumavam entregar render props estilo <Downshift> agora entregam hooks estilo useSelect.

Render props ainda ganham seu lugar onde o provider precisa ser um componente: quando o valor exposto está vinculado a um elemento renderizado específico (medição, posicionamento), ou quando uma biblioteca quer policiar o que renderiza dentro de um componente limite, um que captura um erro de render abaixo ou mantém um estado de carregamento e decide o que aparece em seu lugar. Reconheça o padrão à vista, alcance-o quando o limite do componente em si importa, e prefira um hook quando não.

Uma nota estrutural em Toggle.Display: chamar children(on) durante render significa que Toggle.Display re-invoca essa função e re-renderiza seu valor de retorno a cada alternância de on. Esse é o ponto, e também é o custo; uma render prop é uma subscription, e tudo dentro da função re-renderiza com o estado ao qual se inscreve. Mantenha o corpo da função pequeno e empurre subárvores pesadas para fora dela quando não dependem do valor.

JunoComportamento sem rosto Um componente sem interface é aquele que faz algo sem mostrar nada de seu próprio: um Toggle que rastreia ligado e desligado, enquanto você fornece o que ele envolve. Seus ajudantes renderizam seu conteúdo quando o estado está ligado ou desligado.

E quando você precisa do valor verdadeiro-ou-falso do seu lado, você passa ao componente uma função como seu filho; o componente chama sua função com o valor, e o que sua função retorna é o que aparece na página.

JunoComportamento sem rosto Construa componentes sem interface como uma família composta sobre um context com escopo: um parent que possui estado, uma peça Button que o inverte via um manipulador de clique envolvente, peças On e Off que renderizam filhos condicionalmente. Exponha mudanças com uma prop de evento como onToggle, padrão de noop e disparada de um efeito com proteção de ref de primeiro render.

Quando quem chama precisa do estado bruto, dê a ele uma peça Display que chama children como uma função com o estado, que é o padrão render props.

JunoComportamento sem rosto Componentes sem interface separam comportamento de marcação para que uma lógica sirva widgets visualmente não relacionados; peças de render condicional como On e Off remontam seus filhos, então qualquer coisa precisando continuidade, transições CSS incluídas, requer expor estado em vez disso, via uma render prop.

Trate render props como inversão de controle com JSX e como uma subscription com custo de re-render, e prefira um hook customizado para o mesmo trabalho a menos que o limite do componente em si carregue significado.

Próximo: Custom hooks na prática, onde a mesma lógica se move para uma função e o componente desaparece.