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:
<Toggle>
<Toggle.Button>
<Star />
</Toggle.Button>
<Toggle.On>O toggle está ligado</Toggle.On>
<Toggle.Off>O toggle está desligado</Toggle.Off>
</Toggle>Toggle.Buttonrenderiza seus filhos em um<button type="button">com umonClickque 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.Onrenderiza seus filhos quando o estado é verdadeiro, e null caso contrário.Toggle.Offfaz 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:
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:
function ToggleDisplay({ children }) {
const { on } = useContext(ToggleContext)
return children(on)
}
Toggle.Display = ToggleDisplay<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.
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.
Próximo: Custom hooks na prática, onde a mesma lógica se move para uma função e o componente desaparece.

