Referenciando valores com Refs
Quando você quer que um componente “lembre” de alguma informação, mas você não quer que aquela informação cause novos renders, você pode usar um ref.
Você aprenderá
- Como adicionar um ref ao seu componente
- Como atualizar o valor de um ref
- Como refs diferenciam de state
- Como usar refs de forma segura
Adicionando um ref ao seu componente
Você pode adicionar um ref ao seu componente importando o Hook useRef
do React:
import { useRef } from 'react';
Dentro do seu componente, invoque o Hook useRef
e passe o valor inicial que você quer referenciar como o único argumento. Por exemplo, aqui está um ref para o valor 0
:
const ref = useRef(0);
useRef
retorna um objeto assim:
{
current: 0 // o valor que você passou para o useRef
}
Illustrated by Rachel Lee Nabors
Você pode acessar o valor atual daquele ref através da propriedade ref.current
. Esse valor é intencionalmente mutável, o que significa que você pode tanto ler quanto escrever sobre ele. É como um bolso secreto do seu componente o qual o React não rastreia. (É isso que o faz uma “saída de emergência” do fluxo de data de mão-única do React—mais sobre isso abaixo!)
Aqui, um botão irá incrementar ref.current
a cada clique:
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> ); }
O ref aponta para um número, mas, como state, você pode apontá-lo para qualquer coisa: uma string, um objeto, ou até mesmo uma função. Diferentemente do state, ref é um simples objeto Javascript com a propriedade current
que você pode ler e modificar.
Note que o componente não re-renderiza com cada incremento. Assim como state, refs são retidos pelo React entre re-renderizações. Entretanto, alterar o state re-renderiza um componente. Mudar um ref não!
Exemplo: construindo um cronômetro
Você pode combinar refs e state em um único componente. Por exemplo, vamos fazer um cronômetro que o usuário possa iniciar ou parar ao pressionar um botão. Para exibir quanto tempo passou desde que o usuário pressionou “Start”, você precisará rastrear quando o botão Start foi pressionado e qual o horário atual. Essas informações são usadas para renderização, então as manteremos no state:
const [startTime, setStartTime] = useState(null);
const [now, setNow] = useState(null);
Quando o usuário pressionar “Start”, você usará setInterval
para atualizar o tempo a cada 10 milissegundos:
import { useState } from 'react'; export default function Stopwatch() { const [startTime, setStartTime] = useState(null); const [now, setNow] = useState(null); function handleStart() { // Inicia contagem. setStartTime(Date.now()); setNow(Date.now()); setInterval(() => { // Atualizar o tempo atual a cada 10 milissegundos. setNow(Date.now()); }, 10); } let secondsPassed = 0; if (startTime != null && now != null) { secondsPassed = (now - startTime) / 1000; } return ( <> <h1>Time passed: {secondsPassed.toFixed(3)}</h1> <button onClick={handleStart}> Start </button> </> ); }
Quando o botão “Stop” é pressionado, você precisará cancelar o intervalo existente de forma que ele pare de atualizar a variável now
do state.Você pode fazer isso invocando ‘clearInterval’, mas você precisará passar o ID do intervalo que foi retornado anteriormente pela invocação do setInterval
quando o usuário pressionou Start. Você precisará gravar esse ID do intervalo em algum lugar. Já que o ID do intervalo não é usado para renderização, você pode guardá-lo em um ref:
import { useState, useRef } from 'react'; export default function Stopwatch() { const [startTime, setStartTime] = useState(null); const [now, setNow] = useState(null); const intervalRef = useRef(null); function handleStart() { setStartTime(Date.now()); setNow(Date.now()); clearInterval(intervalRef.current); intervalRef.current = setInterval(() => { setNow(Date.now()); }, 10); } function handleStop() { clearInterval(intervalRef.current); } let secondsPassed = 0; if (startTime != null && now != null) { secondsPassed = (now - startTime) / 1000; } return ( <> <h1>Time passed: {secondsPassed.toFixed(3)}</h1> <button onClick={handleStart}> Start </button> <button onClick={handleStop}> Stop </button> </> ); }
Quando uma informação é usada para renderização, mantenha-a no state. Quando uma informação é necessária somente por manipuladores de eventos (event handlers) e mudá-la não requer uma re-renderização, usar um ref pode ser mais eficiente.
Diferenças entre refs e state
Talvez você esteja pensando que refs parecem ser menos “rigorosos” que state—você pode mutá-los ao invés de sempre ter que usar uma função de definir state, por exemplo. Mas na maioria dos casos, você irá querer usar state. Refs são uma “válvula de escape” que você não precisará com frequência. Aqui uma comparação entre state e refs:
refs | state |
---|---|
useRef(initialValue) retorna { current: initialValue } | useState(initialValue) retorna o valor atual de uma variável de state e uma função setter do state ( [value, setValue] ) |
Não provoca re-renderização quando alterada. | Provoca re-renderização quando alterada. |
Mutável—você pode modificar e atualizar o valor de current de fora do processo de renderização. | “Imutável”—você deve usar a função de definir state para modificar variáveis de state e despachar uma re-renderização. |
Você não deve ler (ou sobrescrever) o valor de current durante uma rerenderização. | Você pode ler state a qualquer momento. Entretanto, cada renderização tem seu snapshot do state o qual não muda. |
Aqui um botão contador que foi implementado com state:
import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); function handleClick() { setCount(count + 1); } return ( <button onClick={handleClick}> You clicked {count} times </button> ); }
Como o valor count
é exibido, faz sentido usar um valor de state para ele. Quando o valor do contador é definido com setCount()
, React re-renderiza o componente e a tela é atualizada para refletir o novo valor.
Se você tentasse implementar isso com um ref, React nunca re-renderizaria o componente, então você nunca veria o contador mudar! Veja como, ao clicar neste botão, seu texto não é atualizado:
import { useRef } from 'react'; export default function Counter() { let countRef = useRef(0); function handleClick() { // Isso não re-renderiza o componente! countRef.current = countRef.current + 1; } return ( <button onClick={handleClick}> You clicked {countRef.current} times </button> ); }
É por isso que ler ref.current
durante a renderização leva a um código não confiável. Se você precisar disso, dê preferência ao state.
Deep Dive
Apesar de ambos useState
e useRef
serem providos pelo React, em princípio useRef
poderia ser implementado em cima de useState
. Você pode imaginar que dentro do React, useRef
é implementado assim:
// Dentro do React
function useRef(initialValue) {
const [ref, unused] = useState({ current: initialValue });
return ref;
}
Durante a primeira renderização, useRef
retorna { current: initialValue }
. Este objeto é armazenado pelo React, então durante a próxima renderização o mesmo objeto será retornado. Note como o setter do state não é utilizado neste exemplo. Ele é desnecessário porque useRef
precisa sempre retornar o mesmo objeto!
O React oferece uma versão integrada do useRef porque é algo comum na prática. No entanto, você pode pensar nele como uma variável de state normal sem um setter. Se você está familiarizado com programação orientada a objetos, refs podem te lembrar dos campos de instância — mas em vez de this.something
você escreve somethingRef.current
.
Quando usar refs
Normalmente, você usará um ref quando o seu componente precisa “sair” do React e se comunicar com APIs externas, frequentemente uma API do navegador que não afetará a aparência do componente. Aqui estão algumas destas situações raras:
- Armazenando IDs de temporizadores (timeouts)
- Armazenando e manipulando elementos DOM, que são abordados na próxima página
- Armazenando outros objetos que não são necessários para calcular o JSX.
Se o seu componente precisa armazenar algum valor, mas isso não afeta a lógica de renderização, escolha refs.
Melhores práticas para refs
Seguir esses princípios tornará seus componentes mais previsíveis:
- Trate refs como uma saída de emergência. Refs são úteis quando você trabalha com sistemas externos ou APIs de navegador. Se grande parte da lógica da sua aplicação e fluxo de dados dependem de refs, talvez seja necessário repensar suaa abordagem.
- Não leia nem escreva sobre
ref.current
durante a renderização. Se alguma informação for necessária durante a renderização, use state. Como o React não sabe quandoref.current
muda, até mesmo a leitura durante a renderização torna o comportamento do seu componente difícil de prever. (A única exceção a isso é código comoif (!ref.current) ref.current = new Thing()
, que define o ref apenas uma vez durante a primeira renderização.)
As limitações do state do React não se aplicam aos refs. Por exemplo, o state age como uma foto instantânea para cada renderização e não atualiza de forma síncrona. Mas quando você altera o valor atual de um ref, ele muda imediatamente:
ref.current = 5;
console.log(ref.current); // 5
Isso é porque o ref em si é um objeto JavaScript normal, e portanto se comporta como um.
Você também não precisa se preocupar em evitar a mutação quando trabalha com um ref. Desde que o objeto que você está mutando não seja usado para renderização, o React não se importa com o que você faz com o ref ou seu conteúdo.
Refs e o DOM
Você pode apontar um ref para qualquer valor. No entanto, o caso de uso mais comum para um ref é acessar um elemento DOM. Por exemplo, isso é útil se você deseja focar um campo de entrada (input) programaticamente. Quando você passa um ref para um atributo ref
em JSX, como <div ref={myRef}>
, o React colocará o elemento DOM correspondente em myRef.current
. Você pode saber mais sobre isso em Manipulando o DOM com Refs.
Recap
- Refs são uma saída de emergência para manter valores que não são usados para renderização. Você não precisará deles com frequência.
- Um ref é um objeto JavaScript simples com uma única propriedade chamada
current
, que você pode ler ou definir. - Você pode solicitar um ref ao React chamando o Hook
useRef
. - Assim como o state, refs permitem que você retenha informações entre re-renderizações de um componente.
- Ao contrário do state, definir o valor
current
do ref não provoca uma re-renderização. - Não leia nem escreva sobre
ref.current
durante a renderização. Isso torna o comportamento do seu componente difícil de prever.
Challenge 1 of 4: Consertar um input de chat quebrado
Digite uma mensagem e clique em “Send”. Você perceberá que há um atraso de três segundos antes de ver o alerta “Sent!“. Durante esse atraso, você pode ver um botão “Undo”. Clique nele. Este botão “Undo” deve impedir que a mensagem “Sent!” apareça. Ele faz isso chamando clearTimeout
para o ID do temporizador salvo durante handleSend
. No entanto, mesmo depois de clicar em “Undo”, a mensagem “Sent!” ainda aparece. Descubra por que isso não funciona e corrija-o.
import { useState } from 'react'; export default function Chat() { const [text, setText] = useState(''); const [isSending, setIsSending] = useState(false); let timeoutID = null; function handleSend() { setIsSending(true); timeoutID = setTimeout(() => { alert('Sent!'); setIsSending(false); }, 3000); } function handleUndo() { setIsSending(false); clearTimeout(timeoutID); } return ( <> <input disabled={isSending} value={text} onChange={e => setText(e.target.value)} /> <button disabled={isSending} onClick={handleSend}> {isSending ? 'Sending...' : 'Send'} </button> {isSending && <button onClick={handleUndo}> Undo </button> } </> ); }