Alguns de seus componentes podem precisar controlar e se sincronizar com sistemas externos ao React. Por exemplo: você talvez precise focar um input através da API do navegador, pausar ou dar continuidade a um player de vídeo implementado sem uso do React ou conectar-se e escutar mensagens vindas de um servidor remoto. Nesse capítulo, você aprenderá as saídas de emergência que te permitem “contornar” o React e conectar-se a sistemas externos. A maior parte da lógica na sua aplicação não deverá depender dessas funcionalidades.
Neste capítulo
- Como “guardar” informação sem rerrenderizar
- Como acessar elementos do DOM gerenciados pelo React
- Como sincronizar componentes com sistemas externos
- Como remover Effects desnecessários dos seus componentes
- Como o ciclo de vida de um Effect difere do de um componente
- Como prevenir alguns valores de executarem Effects novamente
- Como diminuir o número de execuções de um Effect
- Como compartilhar lógica entre componentes
Referenciando valores através de refs
Quando desejar que um componente “guarde” alguma informação, mas não que ela acione novas renderizações, você pode usar uma ref:
const ref = useRef(0);
Assim como estado, refs são retidas pelo React entre rerrenderizações. Entretanto, definir estado rerrenderiza um componente. Mudar uma ref, não! Você pode acessar o valor atual de uma ref através da propriedade ref.current
.
import { useRef } from 'react'; export default function Counter() { let ref = useRef(0); function handleClick() { ref.current = ref.current + 1; alert('You clicked ' + ref.current + ' times!'); } return ( <button onClick={handleClick}> Click me! </button> ); }
Uma ref é como um compartimento secreto do seu componente que o React não acompanha. Por exemplo, você pode usar refs para guardar IDs de timeout(https://developer.mozilla.org/en-US/docs/Web/API/setTimeout#return_value), elementos do DOM e outros objetos que não impactem no resultado da renderização de um componente.
Ready to learn this topic?
Leia Referenciando Valores com Refs para aprender como usar refs para guardar informação.
Read MoreManipulando o DOM com refs
O React automaticamente atualiza o DOM para adequá-lo ao resultado de sua renderização, então seus componentes não precisarão manipulá-lo frequentemente. Entretanto, às vezes você precisa acessar elementos do DOM gerenciados pelo React - por exemplo, para focar em um nó, realizar a rolagem até ele ou medir seu tamanho e posição. Não há método pronto no React que permita fazer isso, então você precisará de uma referência ao nó do DOM. Neste exemplo: clicar no botão atribuirá foco ao input através de uma ref.
import { useRef } from 'react'; export default function Form() { const inputRef = useRef(null); function handleClick() { inputRef.current.focus(); } return ( <> <input ref={inputRef} /> <button onClick={handleClick}> Focus the input </button> </> ); }
Ready to learn this topic?
Leia Manipulando o DOM com refs para aprender como acessar elementos do DOM gerenciados pelo React.
Read MoreSincronizando com Effects
Alguns componentes precisam se sincronizar com sistemas externos. Por exemplo, você talvez precise controlar um componente fora do React com base no estado do React, estabelecer uma conexão com um servidor ou enviar um log de analytics quando um componente aparecer na tela. Diferentemente de handlers de eventos, que permitem tratar eventos específicos, Effects te permitem executar um trecho de código após a renderização. Utilize-os para sincronizar seu componente com o sistema fora do React.
Clique em Play/Pause algumas vezes e veja como o player de vídeo permanece sincronizado com o valor da prop isPlaying
:
import { useState, useRef, useEffect } from 'react'; function VideoPlayer({ src, isPlaying }) { const ref = useRef(null); useEffect(() => { if (isPlaying) { ref.current.play(); } else { ref.current.pause(); } }, [isPlaying]); return <video ref={ref} src={src} loop playsInline />; } export default function App() { const [isPlaying, setIsPlaying] = useState(false); return ( <> <button onClick={() => setIsPlaying(!isPlaying)}> {isPlaying ? 'Pause' : 'Play'} </button> <VideoPlayer isPlaying={isPlaying} src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4" /> </> ); }
Muitos Effects também “se limpam”. Por exemplo, um Effect que estabelece uma conexão com um servidor de chat deveria retornar uma função de limpeza que informa ao React como se desconectar de tal servidor:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; export default function ChatRoom() { useEffect(() => { const connection = createConnection(); connection.connect(); return () => connection.disconnect(); }, []); return <h1>Welcome to the chat!</h1>; }
No ambiente de desenvolvimento, o React irá imediatamente executar e limpar seu Effect uma vez a mais. Por essa razão, "✅ Connecting..."
é exibido duas vezes. Isso assegura que você não se esqueceu de implementar a função de limpeza.
Ready to learn this topic?
Leia Sincronizando com Effects para aprender como sincronizar componentes com sistemas externos.
Read MoreTalvez você não precise de um Effect
Effects são uma saída de emergência do paradigma do React. Eles permitem que você “contorne” o React e sincronize seus componentes com algum sistema externo. Se não houver sistema externo envolvido (por exemplo, se você quiser atualizar o estado de um componente com props ou mudança de estado), você não deveria usar um Effect. Remover Effects desnecessários tornará seu código mais fácil de se entender, mais rápido e menos propenso a erros.
Há dois casos comuns nos quais você não precisa de Effects:
- Você não precisa de Effects para transformar dados para renderização.
- Você não precisa de Effects para lidar com eventos do usuário.
Por exemplo, você não precisa de um Effect para ajustar um estado baseado em outro estado:
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// 🔴 Evite: estado redundante e Effect desnecessário
const [fullName, setFullName] = useState('');
useEffect(() => {
setFullName(firstName + ' ' + lastName);
}, [firstName, lastName]);
// ...
}
Ao invés disso, calcule o quanto puder enquanto estiver renderizando:
function Form() {
const [firstName, setFirstName] = useState('Taylor');
const [lastName, setLastName] = useState('Swift');
// ✅ Bom: calculado durante a renderização
const fullName = firstName + ' ' + lastName;
// ...
}
Entretanto, você precisa de Effects para sincronizar com sistemas externos.
Ready to learn this topic?
Leia Talvez você não precise de um Effect para aprender como remover Effects desnecessários.
Read MoreCiclo de vida de Effects reativos
Effects têm um ciclo de vida diferente dos componentes. Componentes podem se montar, atualizar ou desmontar. Um Effect só pode fazer duas coisas: começar a sincronizar algo e, mais tarde, parar a sincronização. Esse ciclo pode acontecer múltiplas vezes se seu Effect depender de props e estado que possam mudar ao longo do tempo.
Este Effect depende do valor da prop roomId
. Props são valores reativos, o que significa que podem mudar em uma rerrenderização. Note que um Effect ressincroniza (e reconecta ao servidor) caso roomId
seja alterado:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h1>Welcome to the {roomId} room!</h1>; } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
O React provê uma regra de linter para checar se você especificou as dependências do seu Effect corretamente. Caso você se esqueça de especificar roomId
na lista de dependências do exemplo acima, o linter achará esse bug automaticamente.
Ready to learn this topic?
Leia Ciclo de vida de Effects reativos para aprender como o ciclo de vida de um Effect é diferente do de um componente.
Read MoreSeparando eventos de Effects
Handlers de eventos só são executados novamente caso uma interação ocorra de novo. Diferentemente de handlers de eventos, Effects resincronizam se qualquer valor lido por eles, como props ou estado, mudarem desde a última renderização. Às vezes, você deseja uma mistura dos dois comportamentos: um Effect que é executado novamente em resposta a alguns valores, mas não a outros.
Todo código dentro de Effects é reativo. Ele será executado novamente se algum valor reativo lido por ele se alterar por causa de uma rerrenderização. Por exemplo, esse Effect irá reconectar ao chat se roomId
ou theme
tiverem mudado.
import { useState, useEffect } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { showNotification('Connected!', theme); }); connection.connect(); return () => connection.disconnect(); }, [roomId, theme]); return <h1>Welcome to the {roomId} room!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = useState(false); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Use dark theme </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
Isso não é ideal. Você quer se reconectar ao chat apenas se roomId
tiver mudado. Mudar o theme
não deveria reconectar o chat! Mova o código lendo theme
do seu Effect para um Effect Event.
import { useState, useEffect } from 'react'; import { experimental_useEffectEvent as useEffectEvent } from 'react'; import { createConnection, sendMessage } from './chat.js'; import { showNotification } from './notifications.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId, theme }) { const onConnected = useEffectEvent(() => { showNotification('Connected!', theme); }); useEffect(() => { const connection = createConnection(serverUrl, roomId); connection.on('connected', () => { onConnected(); }); connection.connect(); return () => connection.disconnect(); }, [roomId]); return <h1>Welcome to the {roomId} room!</h1> } export default function App() { const [roomId, setRoomId] = useState('general'); const [isDark, setIsDark] = useState(false); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <label> <input type="checkbox" checked={isDark} onChange={e => setIsDark(e.target.checked)} /> Use dark theme </label> <hr /> <ChatRoom roomId={roomId} theme={isDark ? 'dark' : 'light'} /> </> ); }
Código dentro de Effect Events não é reativo, portanto a mudança de theme
deixará de fazer seu Effect reconectar.
Ready to learn this topic?
Leia Separando eventos de Effects para aprender como prevenir alguns valores de dispararem Effects.
Read MoreRemovendo dependências de Effect
Ao escrever um Effect, o linter irá verificar que você incluiu todo valor reativo (como props e estado) lido pelo Effect à lista de dependências desse Effect. Isso assegura que seu Effect permanecerá sincronizado com os valores das props e estado mais atualizados do seu componente. Dependências desnecessárias podem fazer com que seu Effect seja executado muito frequentemente, ou até mesmo crie um loop infinito. A forma de removê-las depende de cada caso.
Por exemplo, esse Effect depende do objeto options
, que é recriado toda vez que você edita o input:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); const options = { serverUrl: serverUrl, roomId: roomId }; useEffect(() => { const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [options]); return ( <> <h1>Welcome to the {roomId} room!</h1> <input value={message} onChange={e => setMessage(e.target.value)} /> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
Você não quer que o chat reconecte toda vez que você começar a digitar uma mensagem nele. Para consertar esse problema, mova a criação do objeto options
para dentro do Effect de forma que o Effect dependa apenas da string roomId
:
import { useState, useEffect } from 'react'; import { createConnection } from './chat.js'; const serverUrl = 'https://localhost:1234'; function ChatRoom({ roomId }) { const [message, setMessage] = useState(''); useEffect(() => { const options = { serverUrl: serverUrl, roomId: roomId }; const connection = createConnection(options); connection.connect(); return () => connection.disconnect(); }, [roomId]); return ( <> <h1>Welcome to the {roomId} room!</h1> <input value={message} onChange={e => setMessage(e.target.value)} /> </> ); } export default function App() { const [roomId, setRoomId] = useState('general'); return ( <> <label> Choose the chat room:{' '} <select value={roomId} onChange={e => setRoomId(e.target.value)} > <option value="general">general</option> <option value="travel">travel</option> <option value="music">music</option> </select> </label> <hr /> <ChatRoom roomId={roomId} /> </> ); }
Note que você não começou editando a lista de dependências para remover options
de lá. Isso seria errado. Ao invés disso, você alterou o código ao redor dela, de forma que essa dependência se tornasse desnecessária. Pense na lista de dependências como uma lista de todos os valores reativos usados pelo código do seu Effect. Você não escolheu intencionalmente o que colocar na lista. A lista descreve seu código. Para mudar a lista de dependências, mude o código.
Ready to learn this topic?
Leia Removendo dependências de Effect para aprender a como diminuir o número de execuções de um Effect.
Read MoreReutilizando lógica com Hooks customizados
O React vem com diversos Hooks prontos, como useState
, useContext
e useEffect
. Às vezes, você desejará que houvesse um Hook para um propósito mais específico: por exemplo, buscar dados, observar se um usuário está online, ou para conectar-se a uma sala de chat. Para fazer isso, você pode criar seus próprios Hooks conforme as necessidades da sua aplicação.
Neste exemplo, o Hook customizado usePointerPosition
acompanha a posição do cursor enquanto o outro Hook customizado useDelayedValue
retorna um valor passado a ele com o atraso de uma quantidade arbitrária de milissegundos. Mova o cursor sobre a àrea de pré-visualização do sandbox para ver um rastro de pontinhos acompanhando a trajetória do cursor:
import { usePointerPosition } from './usePointerPosition.js'; import { useDelayedValue } from './useDelayedValue.js'; export default function Canvas() { const pos1 = usePointerPosition(); const pos2 = useDelayedValue(pos1, 100); const pos3 = useDelayedValue(pos2, 200); const pos4 = useDelayedValue(pos3, 100); const pos5 = useDelayedValue(pos4, 50); return ( <> <Dot position={pos1} opacity={1} /> <Dot position={pos2} opacity={0.8} /> <Dot position={pos3} opacity={0.6} /> <Dot position={pos4} opacity={0.4} /> <Dot position={pos5} opacity={0.2} /> </> ); } function Dot({ position, opacity }) { return ( <div style={{ position: 'absolute', backgroundColor: 'pink', borderRadius: '50%', opacity, transform: `translate(${position.x}px, ${position.y}px)`, pointerEvents: 'none', left: -20, top: -20, width: 40, height: 40, }} /> ); }
Você pode criar Hooks customizados, juntá-los, passar dados entre eles, e reutilizá-los entre componentes. Conforme seu app cresce, vocé escreverá menos Effects à mão porque poderá reutilizar os Hooks customizados que já escreveu. Há muitos Hooks customizados excelentes mantidos pela comunidade React.
Ready to learn this topic?
Leia Reutilizando lógica com Hooks customizados para aprender como compartilhar lógica entre componentes.
Read MoreE agora?
Vá para Referenciando Valores com Refs para começar a ler este capítulo página por página!