Artigo original: The React Cheatsheet for 2021 (+ Real-World Examples)
Elaborei uma ficha informativa visual e abrangente para ajudá-lo a dominar todos os principais conceitos e recursos da biblioteca do React.
Nota da tradução: este texto foi escrito em 2021. Nos últimos três anos, algumas modificações do React podem ter tornado diversas partes deste artigo obsoletas.
Criei esta ficha informativa para ajudá-lo a otimizar seu aprendizado de React no menor tempo possível.
Ela inclui vários exemplos práticos para ilustrar cada recurso da biblioteca e como ela funciona usando padrões que você pode aplicar em seus próprios projetos.
Junto com cada trecho de código, adicionei muitos comentários úteis. Se você ler esses comentários, verá o que cada linha de código faz, como os diferentes conceitos se relacionam entre si e obterá um entendimento mais completo de como o React pode ser usado.
Observe que as palavras-chave particularmente úteis para você conhecer como desenvolvedor do React estão destacadas em negrito. Portanto, fique atento a elas.
- Guia de referência rápida para revisar quando e como quiser
- Inúmeros de trechos de código copiáveis para facilitar a reutilização
- Leia este enorme guia onde for melhor para você. No trem, na sua mesa, na fila... em qualquer lugar.
Há muitas coisas ótimas para tratarmos. Então, vamos começar.
Deseja executar qualquer um dos trechos de código abaixo? Crie uma aplicação em React para experimentar qualquer um desses exemplos usando a ferramenta on-line (gratuita) CodeSandbox. Você pode fazer isso instantaneamente visitando react.new.
Sumário
Fundamentos do React
Hooks essenciais do React
Hooks e desempenho
Hooks avançados do React
Fundamentos do React
Elementos JSX
As aplicações em React são estruturadas usando uma sintaxe chamada JSX. Esta é a sintaxe de um elemento JSX básico.
/*
O JSX nos permite escrever em uma sintaxe quase idêntica à do HTML simples.
O resultado disso é que o JSX é uma ferramenta poderosa para estruturar nossas aplicações.
O JSX usa todas as tags HTML válidas (ou seja, div/span, h1-h6, form/input, img etc.).
*/
<div>Alô, React!</div>
/*
Observação: esse JSX não seria visível porque precisa ser renderizado por nossa aplicação usando ReactDOM.render()
*/
O JSX é a maneira mais comum de estruturar aplicações em React, mas não é obrigatório para o React.
/* O JSX é uma maneira mais simples de usar a função React.createElement()
Em outras palavras, as duas linhas a seguir no React são iguais: */
<div>Alô, React!</div> // sintaxe do JSX
React.createElement('div', null, 'Alô, React!'); // sintaxe do createElement
O JSX não é compreendido pelo navegador. Ele precisa ser compilado em JavaScript puro para que o navegador possa entender.
O compilador mais comumente usado para JSX é chamado Babel.
/*
Quando nosso projeto é criado para ser executado no navegador, nosso JSX será convertido pelo Babel em chamadas simples da função React.createElement().
Disto...
*/
const greeting = <div>Alô React!</div>;
/* ...para isto: */
"use strict";
const greeting = /*#__PURE__*/React.createElement("div", null, "Alô, React!");
O JSX difere do HTML de várias maneiras importantes:
/*
Podemos escrever JSX como se fosse HTML simples, mas na verdade ele é feito usando funções JavaScript.
Como o JSX é JavaScript, e não HTML, há algumas diferenças:
1) Alguns atributos JSX têm nomes diferentes dos atributos HTML. Por quê? Porque algumas palavras de atributos são palavras reservadas em JavaScript, como 'class' (classe). Em vez de class, o JSX usa 'className'.
Além disso, como o JSX é JavaScript, os atributos que consistem em várias palavras são escritos em camelcase:
*/
<div id="header">
<h1 className="title">Alô, React!</h1>
</div>
/*
2) Os elementos JSX que consistem em apenas uma única tag (ou seja, elementos input, img, br) devem ser fechados com uma barra à direita para serem válidos (/):
*/
<input type="email" /> // <input type="email"> é um erro de sintaxe
/*
3) Os elementos JSX que consistem em uma tag de abertura e de fechamento (ou seja, div, span, elemento de botão) devem ter ambas ou ser fechados com uma barra à direita. Como no item 2) acima, é um erro de sintaxe ter um elemento não encerrado.
*/
<button>Clique aqui</button> // somente <button> ou </button> geram um erro de sintaxe
<button /> // vazio, mas também válido
Estilos em linha podem ser adicionados aos elementos JSX usando o atributo style
. Os estilos são atualizados em um objeto, e não em um conjunto de aspas duplas, como no HTML.
Observe que os nomes das propriedades de estilo também devem ser escritos em camelcase.
/*
As propriedades que aceitam valores de pixel (como largura, altura, preenchimento, margem etc.) podem usar números inteiros em vez de cadeias de caracteres.
Por exemplo: fontSize: 22. Em vez de: fontSize: "22px"
*/
<h1 style={{ color: 'blue', fontSize: 22, padding: '0.5em 1em' }}>
Alô, React!
</h1>
Os elementos JSX são expressões JavaScript e podem ser usados como tal. O JSX nos dá todo o poder do JavaScript diretamente em nossa interface de usuário.
/*
Os elementos JSX são expressões (resultam em um valor) e, portanto, podem ser atribuídos a variáveis JavaScript simples...
*/
const greeting = <div>Alô, React!</div>;
const isNewToReact = true;
// ... ou pode ser exibido condicionalmente
function sayGreeting() {
if (isNewToReact) {
// ... ou retornado de funções, etc.
return greeting; // exibe: Alô, React!
} else {
return <div>Olá novamente, React</div>;
}
}
O JSX nos permite inserir (ou incorporar) expressões JavaScript simples usando a sintaxe de chaves:
const year = 2021;
/* Podemos inserir valores JS primitivos (ou seja, strings, números, booleanos) entre chaves: {} */
const greeting = <div>Alô, React, em {year}</div>;
/* Também podemos inserir expressões que resultam em um valor primitivo: */
const goodbye = <div>Adeus ano velho: {year - 1}</div>
/* As expressões também podem ser usadas para atributos de elementos */
const className = 'title';
const title = <h1 className={className}>Meu título</h1>
/* Observação: tentar inserir valores de objetos (por exemplo: objetos, arrays, maps) entre chaves resultará em um erro */
O JSX nos permite aninhar elementos uns nos outros, como faríamos com o HTML.
/*
Para escrever JSX em várias linhas, coloque entre parênteses: ()
As expressões JSX que abrangem várias linhas são chamadas de expressões multilinhas
*/
const greeting = (
// div é o elemento pai
<div>
{/* h1 e p são elementos filhos */}
<h1>Alô!</h1>
<p>Bem vindo ao React</p>
</div>
);
/* 'pais' e 'filhos' são a forma como descrevemos os elementos JSX em relação
uns com os outros, como se estivéssemos falando de elementos HTML */
Os comentários em JSX são escritos como comentários JavaScript de várias linhas, escritos entre chaves, como este:
const greeting = (
<div>
{/* Este é um comentário de uma única linha */}
<h1>Alô!</h1>
<p>Bem vindo ao React</p>
{/* Este é
um comentário
de várias linhas */}
</div>
);
Todas as aplicações em React exigem três coisas:
ReactDOM.render()
: usado para renderizar (exibir) nossa aplicação, montando-a em um elemento HTML- Um elemento JSX: chamado de "nó raiz", porque é a raiz da nossa aplicação. Em outras palavras, ao renderizá-lo, renderizará também todos os filhos dentro dele
- Um elemento HTML (DOM): onde a aplicação é inserida em uma página HTML. O elemento geralmente é uma div com um id de valor "root", localizado em um arquivo index.html.
// Os pacotes podem ser instalados localmente ou trazidos por meio de um link de um CDN (adicionado ao cabeçalho do documento HTML)
import React from "react";
import ReactDOM from "react-dom";
// O nó raiz (geralmente um componente) é mais frequentemente chamado de "App"
const App = <h1>Alô, React!</h1>;
// ReactDOM.render(nó raiz, elemento HTML)
ReactDOM.render(App, document.getElementById("root"));
Componentes e props
O JSX pode ser agrupado em funções individuais chamadas componentes.
Há dois tipos de componentes no React: componentes de função e componentes de classe.
Os nomes de componentes, sejam para componentes de função ou de classe, são capitalizados para distingui-los das funções JavaScript simples que não retornam JSX:
import React from "react";
/*
Componente de função
Observe o nome da função capitalizado: 'Header', e não 'header'
*/
function Header() {
return <h1>Alô, React</h1>;
}
// Os componentes de função que usam uma sintaxe de arrow function também são válidos
const Header = () => <h1>Alô, React</h1>;
/*
Componente de classe
Os componentes de classe têm mais boilerplate (observe a palavra-chave 'extends' e o método 'render'.)
*/
class Header extends React.Component {
render() {
return <h1>Alô, React</h1>;
}
}
Os componentes, apesar de serem funções, não são chamados como funções JavaScript comuns. Eles são executados ao renderizá-los como faríamos com o JSX em nossa aplicação.
// Chamamos esse componente de função como uma função normal?
// Não, para executá-las e exibir o JSX que elas retornam...
const Header = () => <h1>Alô, React</h1>;
// ...usamos como elementos JSX 'personalizados'.
ReactDOM.render(<Header />, document.getElementById("root"));
// renderiza: <h1>Alô, React</h1>
A grande vantagem dos componentes é sua capacidade de serem reutilizados em todas as aplicações, sempre que necessário.
Como os componentes aproveitam o poder das funções JavaScript, podemos passar dados logicamente para eles, como faríamos ao passar um ou mais argumentos.
/*
Os componentes Header e Footer podem ser reutilizados em qualquer página da nossa aplicação.
Os componentes eliminam a necessidade de reescrever o mesmo JSX várias vezes.
*/
// Componente IndexPage, visível na rota '/' de nosso aplicativo
function IndexPage() {
return (
<div>
<Header />
<Hero />
<Footer />
</div>
);
}
// Componente AboutPage, visível na rota '/about'.
function AboutPage() {
return (
<div>
<Header />
<About />
<Testimonials />
<Footer />
</div>
);
}
Os dados passados aos componentes em JavaScript são chamados de props. As props são idênticas aos atributos dos elementos JSX/HTML simples, mas você pode acessar seus valores dentro do próprio componente.
Props estão disponíveis nos parâmetros do componente para o qual são passadas. Props são sempre incluídas como propriedades de um objeto.
/*
O que fazemos se quisermos passar dados personalizados para o nosso componente a partir de um componente pai?
Por exemplo, para exibir o nome do usuário no cabeçalho da aplicação.
*/
const username = "John";
/*
Para isso, adicionamos 'atributos' personalizados ao nosso componente, chamados props.
Podemos adicionar muitos deles como quisermos e dar a eles nomes adequados aos dados que passamos.
Para passar o nome do usuário para o cabeçalho, usamos uma prop que chamamos apropriadamente de 'username'
*/
ReactDOM.render(
<Header username={username} />,
document.getElementById("root")
);
// Chamamos essa prop de 'username', mas podemos usar qualquer identificador válido que daríamos, por exemplo, uma variável JavaScript
// props é o objeto que cada componente recebe como argumento
function Header(props) {
// as props que criamos no componente (username)
// se tornam propriedades no objeto props
return <h1>Hello {props.username}</h1>;
}
As props nunca devem ser alteradas diretamente no componente filho.
Outra maneira de dizer isso é que as props nunca devem sofrer mutação, pois são um objeto JavaScript simples.
/*
Os componentes devem operar como funções 'puras'.
Ou seja, para cada entrada, devemos poder esperar o mesmo resultado.
Isso significa que não podemos alterar o objeto props, apenas ler a partir dele.
*/
// Não podemos modificar o objeto props :
function Header(props) {
props.username = "Doug";
return <h1>Hello {props.username}</h1>;
}
/*
O que faríamos se quiséssemos modificar um valor de prop que é passado para o nosso componente?
É nesse caso que usaríamos o state (consulte a seção useState).
*/
A prop de filhos é útil se quisermos passar elementos/componentes como props para outros componentes.
// Podemos aceitar elementos (ou componentes) React como props?
// Sim, por meio de uma propriedade especial no objeto props chamada 'children'
function Layout(props) {
return <div className="container">{props.children}</div>;
}
// A propriedade children é muito útil para quando você deseja que o mesmo
// componente (como um componente Layout) para envolver todos os outros componentes:
function IndexPage() {
return (
<Layout>
<Header />
<Hero />
<Footer />
</Layout>
);
}
// página diferente, mas usa o mesmo componente Layout (graças à prop children)
function AboutPage() {
return (
<Layout>
<About />
<Footer />
</Layout>
);
}
Mais uma vez, como os componentes são expressões JavaScript, podemos usá-los em combinação com instruções if-else e switch para exibir conteúdo de modo condicional, como neste exemplo:
function Header() {
const isAuthenticated = checkAuth();
/* se o usuário estiver autenticado, mostrar a aplicação autenticada; caso contrário, a aplicação não autenticado */
if (isAuthenticated) {
return <AuthenticatedApp />
} else {
/* Como alternativa, podemos eliminar a seção else e fornecer um simples retorno, e a condicional funcionará da mesma maneira */
return <UnAuthenticatedApp />
}
}
Para usar condições no JSX retornado de um componente, você pode usar o operador ternário ou o curto-circuito (operadores && e ||).
function Header() {
const isAuthenticated = checkAuth();
return (
<nav>
{/* Se isAuth for verdadeiro, não mostrará nada. Se for falso, mostre Logo */}
{isAuthenticated || <Logo />}
{/* Se isAuth for verdadeiro, exibirá AuthenticatedApp. Se for falso, exibirá Login */}
{isAuthenticated ? <AuthenticatedApp /> : <LoginScreen />}
{/* Se isAuth for verdadeiro, exibirá o Footer. Se for falso, não mostrará nada */}
{isAuthenticated && <Footer />}
</nav>
);
}
Fragments são componentes especiais para exibir vários componentes sem adicionar um elemento extra ao DOM. Eles são ideais para a lógica condicional que tem vários componentes ou elementos adjacentes.
/*
Podemos aprimorar a lógica do exemplo anterior.
Se isAuthenticated for verdadeiro, como exibimos os componentes AuthenticatedApp e Footer?
*/
function Header() {
const isAuthenticated = checkAuth();
return (
<nav>
<Logo />
{/*
Podemos renderizar os dois componentes com um fragment.
Fragments são muito concisos: <> </>
*/}
{isAuthenticated ? (
<>
<AuthenticatedApp />
<Footer />
</>
) : (
<Login />
)}
</nav>
);
}
/*
Observação: uma sintaxe alternativa para fragments é React.Fragment:
<React.Fragment>
<AuthenticatedApp />
<Footer />
</React.Fragment>
*/
Listas e chaves
Use a função .map() para converter listas de dados (arrays) em listas de elementos.
const people = ["John", "Bob", "Fred"];
const peopleList = people.map(person => <p>{person}</p>);
.map()
pode ser usado para componentes, bem como para elementos JSX simples.
function App() {
const people = ['John', 'Bob', 'Fred'];
// pode interpolar a lista retornada de elementos em {}
return (
<ul>
{/* estamos passando cada elemento do array como props para Person */}
{people.map(person => <Person name={person} />}
</ul>
);
}
function Person({ name }) {
// acessamos a propriedade 'name' diretamente usando a desestruturação de objetos
return <p>This person's name is: {name}</p>;
}
Cada elemento do React em uma lista de elementos precisa de uma key prop especial. As keys são essenciais para que o React possa manter o controle de cada elemento que está sendo iterado com a função .map()
.
O React usa keys (em português, chaves) para atualizar com desempenho elementos individuais quando seus dados são alterados (em vez de renderizar novamente a lista inteira).
As keys precisam ter valores únicos para que seja possível identificar cada uma delas de acordo com o valor da key.
function App() {
const people = [
{ id: 'Ksy7py', name: 'John' },
{ id: '6eAdl9', name: 'Bob' },
{ id: '6eAdl9', name: 'Fred' },
];
return (
<ul>
{/* as chaves precisam ser valores primitivos, de preferência uma string exclusiva, como um ID */}
{people.map(person =>
<Person key={person.id} name={person.name} />
)}
</ul>
);
}
// Se você não tiver alguns IDs em seu conjunto de dados que sejam valores únicos e primitivos, use o segundo parâmetro de .map() para obter o índice de cada elemento.
function App() {
const people = ['John', 'Bob', 'Fred'];
return (
<ul>
{/* usar o índice do elemento do array como chave */}
{people.map((person, i) => <Person key={i} name={person} />)}
</ul>
);
}
Ouvintes de eventos e tratamento de eventos
A escuta de eventos em elementos JSX em contraste com elementos HTML difere em alguns aspectos importantes.
Primeiro, você não pode ouvir eventos em componentes do React – somente em elementos JSX. Adicionar uma propriedade chamada onClick
, por exemplo, a um componente do React seria apenas outra propriedade adicionada ao objeto props.
/*
A convenção para a maioria das funções de manipulador de eventos é prefixá-las com a palavra 'handle' e, em seguida, com a ação que elas executam (por exemplo, handleToggleTheme)
*/
function handleToggleTheme() {
// código para alternar o tema da aplicação
}
/* Em HTML, onclick está todo em letras minúsculas, e o manipulador de eventos inclui um conjunto de parênteses após ser referenciado */
<button onclick="handleToggleTheme()">
Alterar o tema
</button>
/*
Em JSX, onClick é camelcase, como os atributos / props.
Também passamos uma referência à função com chaves.
*/
<button onClick={handleToggleTheme}>
Alterar o tema
</button>
Os eventos do React mais importantes a serem conhecidos são onClick
, onChange
e onSubmit
.
onClick
lida com eventos de clique em elementos JSX (ou seja, em botões)onChange
trata de eventos de teclado (ou seja, um usuário digitando em um campo de input ou textarea)onSubmit
trata os envios de formulários do usuário
function App() {
function handleInputChange(event) {
/* Ao passar a função para um manipulador de eventos, como onChange, obtemos acesso aos dados sobre o evento (um objeto) */
const inputText = event.target.value; // texto digitado no input
const inputName = event.target.name; // 'email' do atributo name
}
function handleClick(event) {
/* O onClick normalmente não precisa de dados de eventos, mas também recebe dados de eventos que podemos usar */
console.log('clicked!');
const eventType = event.type; // "click"
const eventTarget = event.target; // <button>Enviar</button>
}
function handleSubmit(event) {
/*
Quando pressionarmos o botão de retorno, o formulário será enviado, assim como quando um botão com type=“submit” for clicado.
Chamamos event.preventDefault() para evitar que o comportamento padrão do formulário ocorra, que é enviar uma solicitação HTTP e recarregar a página.
*/
event.preventDefault();
const formElements = event.target.elements; // acessar todos os elementos do formulário
const inputValue = event.target.elements.emailAddress.value; // acessar o valor do elemento de entrada com o id “emailAddress”
}
return (
<form onSubmit={handleSubmit}>
<input id="emailAddress" type="email" name="email" onChange={handleInputChange} />
<button onClick={handleClick}>Enviar</button>
</form>
);
}
Hooks essenciais do React
State e useState
O hook useState
nos fornece o state (em português, estado) em um componente de função. O state nos permite acessar e atualizar determinados valores em nossos componentes ao longo do tempo.
O state do componente local é gerenciado pelo hook useState
do React, que nos fornece uma variável de state e uma função que nos permite atualizá-la.
Quando chamamos useState
, podemos dar ao nosso state um valor padrão, fornecendo-o como o primeiro argumento quando chamamos useState
.
import React from 'react';
/*
Como você cria uma variável do state?
Syntax: const [stateVariable] = React.useState(defaultValue);
*/
function App() {
const [language] = React.useState('JavaScript');
/*
Usamos a desestruturação do array para declarar a variável do state.
Como qualquer variável, declaramos que podemos dar a ela o nome que quisermos (neste caso, 'language').
*/
return <div>Estou aprendendo {language}</div>;
}
Observação: qualquer hook nesta seção é da biblioteca principal do React e pode ser importado individualmente.
import React, { useState } from "react";
function App() {
const [language] = useState("javascript");
return <div>Estou aprendendo {language}</div>;
}
useState
também nos fornece uma função 'setter' para atualizar o state depois que ele é criado.
function App() {
/*
A função setter é sempre o segundo valor desestruturado.
A convenção de nomenclatura da função setter deve ser prefixada com 'set'.
*/
const [language, setLanguage] = React.useState("javascript");
return (
<div>
<button onClick={() => setLanguage("python")}>
Aprenda Python
</button>
{/*
Por que usar uma arrow function em linha aqui em vez de chamá-la imediatamente assim: onClick={setterFn()}?
Se fosse assim, setLanguage seria chamado imediatamente e não quando o usuário clicasse no botão.
*/}
<p>Agora estou aprendendo {language}</p>
</div>
);
}
/*
Observação: sempre que a função setter é chamada, o state é atualizado,
e o componente da aplicação é renderizado novamente para exibir o novo state.
Sempre que o state for atualizado, o componente será renderizado novamente.
*/
useState
pode ser usado uma ou várias vezes em um único componente. Ele pode aceitar valores primitivos ou objetos para gerenciar o state.
function App() {
const [language, setLanguage] = React.useState("python");
const [yearsExperience, setYearsExperience] = React.useState(0);
return (
<div>
<button onClick={() => setLanguage("javascript")}>
Alterar a linguagem para JS
</button>
<input
type="number"
value={yearsExperience}
onChange={event => setYearsExperience(event.target.value)}
/>
<p>Agora estou aprendendo {language}</p>
<p>Eu tenho {yearsExperience} anos de experiência</p>
</div>
);
}
Se o novo state depender do anterior, para garantir que a atualização seja feita de maneira confiável, podemos usar uma função dentro da função setter que nos forneça o state anterior correto.
/* Temos a opção de organizar o state usando o tipo de dado mais adequado, de acordo com os dados que estamos gerenciando */
function App() {
const [developer, setDeveloper] = React.useState({
language: "",
yearsExperience: 0
});
function handleChangeYearsExperience(event) {
const years = event.target.value;
/* Devemos passar o objeto de state anterior que tínhamos com o operador spread para distribuir todas as suas propriedades */
setDeveloper({ ...developer, yearsExperience: years });
}
return (
<div>
{/* Não há necessidade de obter o state anterior aqui; estamos substituindo o objeto inteiro */}
<button
onClick={() =>
setDeveloper({
language: "javascript",
yearsExperience: 0
})
}
>
Alterar a linguagem para JS
</button>
{/* Também podemos passar uma referência para a função */}
<input
type="number"
value={developer.yearsExperience}
onChange={handleChangeYearsExperience}
/>
<p>Agora estou aprendendo {developer.language}</p>
<p>Eu tenho {developer.yearsExperience} anos de experiência</p>
</div>
);
}
Se você estiver gerenciando vários valores primitivos, usar o useState
várias vezes geralmente é melhor do que usá-lo uma vez com um objeto. Você não precisa se preocupar em esquecer de combinar o state antigo com o novo.
function App() {
const [developer, setDeveloper] = React.useState({
language: "",
yearsExperience: 0,
isEmployed: false
});
function handleToggleEmployment(event) {
/* Obtemos o valor da variável do state anterior nos parâmetros.
Podemos nomear 'prevState' como quisermos.
*/
setDeveloper(prevState => {
return { ...prevState, isEmployed: !prevState.isEmployed };
// É essencial retornar o novo estado a partir dessa função
});
}
return (
<button onClick={handleToggleEmployment}>Alternar status de emprego</button>
);
}
Efeitos colaterais e useEffect
useEffect
nos permite executar efeitos colaterais em componentes de função. Então, o que são efeitos colaterais?
Efeitos colaterais ocorrem quando precisamos entrar em contato com o mundo externo. Por exemplo, buscar dados de uma API ou trabalhar com o DOM.
São ações que podem alterar o state do nosso componente de modo imprevisível (causando 'efeitos colaterais').
useEffect
aceita uma função de callback (chamada de função de 'efeito') que, por padrão, será executada sempre que houver uma nova renderização.
Ela é executada quando nosso componente é montado, que é o momento certo para executar um efeito colateral no ciclo de vida do componente.
/* O que o nosso código faz? Escolhe uma cor do array de cores e a transforma em cor de fundo */
import React, { useState, useEffect } from 'react';
function App() {
const [colorIndex, setColorIndex] = useState(0);
const colors = ["blue", "green", "red", "orange"];
/*
Estamos causando um 'efeito colateral', pois estamos trabalhando com uma API.
Estamos trabalhando com o DOM, uma API do navegador fora do React.
*/
useEffect(() => {
document.body.style.backgroundColor = colors[colorIndex];
});
/* Sempre que o state é atualizado, a aplicação é renderizada novamente e o useEffect é executado */
function handleChangeColor() {
/* Esse código pode parecer complexo, mas tudo o que ele faz é ir para a próxima cor no array 'colors' e, se estiver na última cor, voltar ao início */
const nextIndex = colorIndex + 1 === colors.length ? 0 : colorIndex + 1;
setColorIndex(nextIndex);
}
return (
<button onClick={handleChangeColor}>
Alterar a cor do fundo
</button>
);
}
Para evitar a execução da chamada da callback do efeito após cada renderização, fornecemos um segundo argumento, um array vazio.
function App() {
...
/*
Com um array vazio, nosso botão não funciona, não importa quantas vezes clicarmos nele...
A cor de fundo é definida apenas uma vez, quando o componente é montado pela primeira vez.
*/
useEffect(() => {
document.body.style.backgroundColor = colors[colorIndex];
}, []);
/*
Como evitar que a função de efeito seja executada a cada atualização de estado e, ainda assim, fazer com que ela funcione sempre que o botão for clicado?
*/
return (
<button onClick={handleChangeIndex}>
Alterar a cor do fundo
</button>
);
}
useEffect
nos permite executar efeitos condicionalmente com o array de dependências.
Um array de dependências é o segundo argumento e, se qualquer um dos valores do array for alterado, a função de efeito será executada novamente.
function App() {
const [colorIndex, setColorIndex] = React.useState(0);
const colors = ["blue", "green", "red", "orange"];
/*
Vamos adicionar colorIndex ao nosso array de dependências
Quando o colorIndex for alterado, o useEffect executará a função de efeito novamente
*/
useEffect(() => {
document.body.style.backgroundColor = colors[colorIndex];
/*
Quando usamos o useEffect, precisamos pensar em quais valores de state
queremos que nosso efeito colateral seja sincronizado
*/
}, [colorIndex]);
function handleChangeIndex() {
const next = colorIndex + 1 === colors.length ? 0 : colorIndex + 1;
setColorIndex(next);
}
return (
<button onClick={handleChangeIndex}>
Alterar a cor do fundo
</button>
);
}
useEffect
nos permite cancelar a assinatura de determinados efeitos retornando uma função no final.
function MouseTracker() {
const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
React.useEffect(() => {
// .addEventListener() configura um ouvinte ativo...
window.addEventListener("mousemove", handleMouseMove);
/* ...Portanto, quando saímos desta página, ele precisa ser
removido para parar de ouvir. Caso contrário, ele tentará definir o
state em um componente que não existe (causando um erro)
Cancelamos a inscrição de todas as inscrições/ouvintes com essa “função de limpeza”)
*/
return () => {
window.removeEventListener("mousemove", handleMouseMove);
};
}, []);
function handleMouseMove(event) {
setMousePosition({
x: event.pageX,
y: event.pageY
});
}
return (
<div>
<h1>A posição atual do mouse é:</h1>
<p>
X: {mousePosition.x}, Y: {mousePosition.y}
</p>
</div>
);
}
useEffect
é o hook a ser usado quando você quiser fazer uma solicitação HTTP (ou seja, uma solicitação GET quando o componente for montado).
Observe que o tratamento de promises com a sintaxe mais concisa async/await exige a criação de uma função separada. (Por quê? Porque a função de callback do efeito não pode ser assíncrona).
const endpoint = "https://api.github.com/users/reedbarger";
// Usando funções de callback .then() para resolver a promise
function App() {
const [user, setUser] = React.useState(null);
React.useEffect(() => {
fetch(endpoint)
.then(response => response.json())
.then(data => setUser(data));
}, []);
}
// Usando a sintaxe async/ await para resolver a promise:
function App() {
const [user, setUser] = React.useState(null);
// não é possível tornar assíncrona a função de callback useEffect
React.useEffect(() => {
getUser();
}, []);
// Devemos aplicar a palavra-chave async a uma função separada
async function getUser() {
const response = await fetch(endpoint);
const data = await response.json();
setUser(data);
}
}
Refs e useRef
Refs são um atributo especial que está disponível em todos os componentes do React. Eles nos permitem criar uma referência a um determinado elemento/componente quando o componente é montado.
O useRef
nos permite usar facilmente as refs
do React. Chamamos useRef (na parte superior do componente) e anexamos o valor retornado ao atributo ref
do elemento para fazer referência a ele.
Depois de criarmos uma referência, usamos a propriedade current
para modificar (silenciar) as propriedades do elemento ou podemos chamar quaisquer métodos disponíveis nesse elemento (como .focus()
para focalizar uma entrada).
function App() {
const [query, setQuery] = React.useState("react hooks");
/* Podemos passar um valor padrão para useRef.
Não precisamos dele aqui, então passamos null para fazer referência a um objeto vazio
*/
const searchInput = useRef(null);
function handleClearSearch() {
/*
.current faz referência ao elemento de entrada na montagem
useRef pode armazenar basicamente qualquer valor em sua propriedade .current
*/
searchInput.current.value = "";
searchInput.current.focus();
}
return (
<form>
<input
type="text"
onChange={event => setQuery(event.target.value)}
ref={searchInput}
/>
<button type="submit">Buscar</button>
<button type="button" onClick={handleClearSearch}>
Limpar
</button>
</form>
);
}
Hooks e desempenho
Prevenção de novas renderizações e React.memo
React.memo
é uma função que nos permite otimizar o modo como nossos componentes são renderizados.
Em particular, ela executa um processo chamado memoização que nos ajuda a evitar que nossos componentes sejam renderizados novamente quando não for necessário (consulte React.useMemo para obter uma definição mais completa de memoização).
O React.memo
ajuda mais a evitar que listas de componentes sejam renderizadas novamente quando seus componentes pai são renderizados novamente.
/*
Na aplicação a seguir, estamos mantendo o controle de nossas habilidades de programação. Podemos criar novas habilidades usando uma entrada, e elas são adicionadas à lista (mostrada no componente SkillList). Se clicarmos em uma habilidade, ela será excluída.
*/
function App() {
const [skill, setSkill] = React.useState('')
const [skills, setSkills] = React.useState([
'HTML', 'CSS', 'JavaScript'
])
function handleChangeInput(event) {
setSkill(event.target.value);
}
function handleAddSkill() {
setSkills(skills.concat(skill))
}
return (
<>
<input onChange={handleChangeInput} />
<button onClick={handleAddSkill}>Adicionar habilidade</button>
<SkillList skills={skills} />
</>
);
}
/* O problema, se você mesmo executar esse código, é que, quando digitamos no input, como o componente pai da SkillList (App) é renderizado novamente, devido à atualização do state a cada pressionamento de tecla, a SkillList é renderizada constantemente (conforme indicado pelo console.log) */
/* No entanto, quando envolvemos o componente SkillList no React.memo (que é uma função de ordem superior, o que significa que ele aceita uma função como argumento), ele não será mais renderizado desnecessariamente quando nosso componente pai o fizer. */
const SkillList = React.memo(({ skills }) => {
console.log('renderizando novamente');
return (
<ul>
{skills.map((skill, i) => <li key={i}>{skill}</li>)}
</ul>
)
})
export default App
Funções de callback e useCallback
useCallback
é um hook usado para melhorar o desempenho do nosso componente. Funções de callback são os nomes das funções que são "chamadas de volta" em um componente pai.
O uso mais comum é ter um componente pai com uma variável de state, mas você deseja atualizar esse estado em um componente filho. O que você faz? Você passa uma função de callback para o filho a partir do pai. Isso nos permite atualizar o state no componente pai.
useCallback
funciona de maneira semelhante ao React.memo
. Ele memoriza as funções de callback, para que não sejam recriadas a cada nova renderização. O uso correto do useCallback
pode melhorar o desempenho da nossa aplicação.
/* Vamos manter exatamente a mesma aplicação acima com o React.memo, mas adicionar uma pequena funcionalidade. Vamos permitir a exclusão de uma habilidade quando clicarmos nela. Para fazer isso, precisamos filtrar o array de habilidades de acordo com a habilidade em que clicamos. Para isso, criamos a função handleRemoveSkill no App */
function App() {
const [skill, setSkill] = React.useState('')
const [skills, setSkills] = React.useState([
'HTML', 'CSS', 'JavaScript'
])
function handleChangeInput(event) {
setSkill(event.target.value);
}
function handleAddSkill() {
setSkills(skills.concat(skill))
}
function handleRemoveSkill(skill) {
setSkills(skills.filter(s => s !== skill))
}
/* Em seguida, passamos handleRemoveSkill como uma prop ou, como se trata de uma função, como uma função de callback a ser usada na SkillList */
return (
<>
<input onChange={handleChangeInput} />
<button onClick={handleAddSkill}>Adicionar habilidade</button>
<SkillList skills={skills} handleRemoveSkill={handleRemoveSkill} />
</>
);
}
/* Quando tentamos digitar a entrada novamente, vemos uma nova renderização no console toda vez que digitamos. Nossa memoização do React.memo está quebrada!
O que está acontecendo é que a função de callback handleRemoveSkill está sendo recriada toda vez que a aplicação é renderizada, fazendo com que todos os filhos também sejam renderizados. Precisamos envolver handleRemoveSkill em useCallback e fazer com que ela seja recriada somente quando o valor da habilidade for alterado.
Para corrigir nossa aplicação, substitua handleRemoveSkill por:
const handleRemoveSkill = React.useCallback((skill) => {
setSkills(skills.filter(s => s !== skill))
}, [skills])
Experimente você mesmo!
*/
const SkillList = React.memo(({ skills, handleRemoveSkill }) => {
console.log('renderizando novamente');
return (
<ul>
{skills.map(skill => <li key={skill} onClick={() => handleRemoveSkill(skill)}>{skill}</li>)}
</ul>
)
})
export default App
Memoização e useMemo
useMemo
é muito semelhante ao useCallback
e serve para melhorar o desempenho. Em vez de ser para callbacks, no entanto, ele serve para armazenar os resultados de cálculos custosos.
useMemo
nos permite memoizar ou lembrar o resultado de cálculos custosos quando eles já foram feitos para determinadas entradas.
Memoização significa que, se um cálculo já foi feito antes com uma determinada entrada, não há necessidade de fazê-lo novamente, pois já temos o resultado armazenado dessa operação.
useMemo
retorna um valor do cálculo, que é então armazenado em uma variável.
/* Com base em nossa aplicação de habilidades, vamos adicionar um recurso para pesquisar nossas habilidades disponíveis por meio de uma entrada de pesquisa adicional. Podemos adicionar isso em um componente chamado SearchSkills (mostrado acima da nossa SkillList).
*/
function App() {
const [skill, setSkill] = React.useState('')
const [skills, setSkills] = React.useState([
'HTML', 'CSS', 'JavaScript', ...milhares de outros itens
])
function handleChangeInput(event) {
setSkill(event.target.value);
}
function handleAddSkill() {
setSkills(skills.concat(skill))
}
const handleRemoveSkill = React.useCallback((skill) => {
setSkills(skills.filter(s => s !== skill))
}, [skills])
return (
<>
<SearchSkills skills={skills} />
<input onChange={handleChangeInput} />
<button onClick={handleAddSkill}>Adicionar habilidade</button>
<SkillList skills={skills} handleRemoveSkill={handleRemoveSkill} />
</>
);
}
/* Vamos imaginar que temos uma lista de milhares de habilidades que queremos pesquisar. Como podemos encontrar e mostrar de modo eficiente as habilidades que correspondem ao nosso termo de pesquisa à medida que o usuário digita? */
function SearchSkills() {
const [searchTerm, setSearchTerm] = React.useState('');
/* Usamos o React.useMemo para memorizar (lembrar) o valor retornado de nossa operação de pesquisa e executá-lo somente quando o searchTerm for alterado */
const searchResults = React.useMemo(() => {
return skills.filter((s) => s.includes(searchTerm);
}), [searchTerm]);
function handleSearchInput(event) {
setSearchTerm(event.target.value);
}
return (
<>
<input onChange={handleSearchInput} />
<ul>
{searchResults.map((result, i) => <li key={i}>{result}</li>
</ul>
</>
);
}
export default App
Hooks avançados no React
Contexto e useContext
No React, queremos evitar o seguinte problema de criar várias props para passar dados para dois ou mais níveis abaixo de um componente principal.
/*
O React Context nos ajuda a evitar a criação de várias props duplicadas.
Esse padrão também é chamado de perfuração de props.
*/
/* Nesta aplicação, queremos passar os dados do usuário para o componente Header, mas primeiro eles precisam passar por um componente Main que não os utiliza */
function App() {
const [user] = React.useState({ name: "Fred" });
return (
// Primeira prop 'user'
<Main user={user} />
);
}
const Main = ({ user }) => (
<>
{/* Segunda prop 'user' */}
<Header user={user} />
<div>Conteúdo do Main...</div>
</>
);
const Header = ({ user }) => <header>Bem vindo, {user.name}!</header>;
O contexto é útil para transmitir props em vários níveis de componentes filhos a partir de um componente pai.
/*
Aqui está o exemplo anterior reescrito com o Context.
Primeiro, criamos o contexto, no qual podemos passar os valores padrão.
Chamamos isso de "UserContext" porque estamos transmitindo dados do usuário
*/
const UserContext = React.createContext();
function App() {
const [user] = React.useState({ name: "Fred" });
return (
{/*
Envolvemos o componente pai com a propriedade Provider.
Passamos os dados pela árvore de componentes na propriedade value.
*/}
<UserContext.Provider value={user}>
<Main />
</UserContext.Provider>
);
}
const Main = () => (
<>
<Header />
<div>Conteúdo do app Main</div>
</>
);
/*
Não podemos remover as duas propriedades 'user'. Em vez disso, podemos simplesmente usar a propriedade Consumer para consumir os dados onde precisamos deles
*/
const Header = () => (
{/* Usamos um padrão chamado render props para obter acesso aos dados */}
<UserContext.Consumer>
{user => <header>Bem vindo, {user.name}!</header>}
</UserContext.Consumer>
);
O hook useContext
nos permite consumir o contexto em qualquer componente de função que seja filho do provedor, em vez de usar o padrão de renderização das props.
function Header() {
/* Passamos o objeto de contexto inteiro para consumi-lo e podemos remover as tags de Consumer */
const user = React.useContext(UserContext);
return <header>Bem vindo, {user.name}!</header>;
};
Redutores e useReducer
Os redutores são funções simples e previsíveis (puras) que recebem um objeto de state anterior e um objeto de ação e retornam um novo objeto de state.
/* Esse redutor gerencia o state do usuário em nossa aplicação: */
function userReducer(state, action) {
/* Os redutores geralmente usam um comando switch para atualizar o state de uma maneira ou de outra com base na propriedade de tipo da ação */
switch (action.type) {
/* Se action.type tiver a string 'LOGIN', obteremos dados do objeto payload na ação */
case "LOGIN":
return {
username: action.payload.username,
email: action.payload.email
isAuth: true
};
case "SIGNOUT":
return {
username: "",
email: "",
isAuth: false
};
default:
/* Se nenhum caso corresponder à ação recebida, retorne ao state anterior */
return state;
}
}
Redutores são um padrão poderoso para gerenciar states, usado na popular biblioteca de gerenciamento de state do Redux (comumente usada com o React).
Os redutores podem ser usados no React com o hook useReducer
para gerenciar o state em toda a aplicação, em comparação com o useState (que é para o state do componente local).
O useReducer
pode ser combinado com o useContext
para gerenciar dados e transmiti-los facilmente entre os componentes.
Assim, useReducer
+ useContext
pode ser um sistema completo de gerenciamento de state para nossas aplicações.
const initialState = { username: "", isAuth: false };
function reducer(state, action) {
switch (action.type) {
case "LOGIN":
return { username: action.payload.username, isAuth: true };
case "SIGNOUT":
// também poderia se espalhar no initialState aqui
return { username: "", isAuth: false };
default:
return state;
}
}
function App() {
// useReducer requer uma função redutora para ser usada e um initialState
const [state, dispatch] = useReducer(reducer, initialState);
// Obtemos o resultado atual do redutor em 'state'
// usamos o dispatch para 'despachar' ações, para executar nosso redutor
// com os dados de que ele precisa (o objeto de ação)
function handleLogin() {
dispatch({ type: "LOGIN", payload: { username: "Ted" } });
}
function handleSignout() {
dispatch({ type: "SIGNOUT" });
}
return (
<>
Usuário atual: {state.username}, isAuthenticated: {state.isAuth}
<button onClick={handleLogin}>Login</button>
<button onClick={handleSignout}>Signout</button>
</>
);
}
Escrevendo hooks personalizados
Os hooks foram criados para reutilizar facilmente um comportamento entre componentes, da mesma forma que os componentes foram criados para reutilizar a estrutura em nossa aplicação.
Os hooks nos permitem adicionar funcionalidades personalizadas às nossas aplicações que atendam às nossas necessidades e podem ser combinados com todos os hooks existentes que abordamos.
Os hooks também podem ser incluídos em bibliotecas de terceiros para o bem de todos os desenvolvedores do React. Há muitas bibliotecas React excelentes que fornecem hooks personalizados, como @apollo/client
, react-query
, swr
e outras.
/* Aqui está um hook personalizado do React chamado useWindowSize que escrevi para calcular o tamanho da janela (largura e altura) de qualquer componente no qual ele é usado */
import React from "react";
export default function useWindowSize() {
const isSSR = typeof window !== "undefined";
const [windowSize, setWindowSize] = React.useState({
width: isSSR ? 1200 : window.innerWidth,
height: isSSR ? 800 : window.innerHeight,
});
function changeWindowSize() {
setWindowSize({ width: window.innerWidth, height: window.innerHeight });
}
React.useEffect(() => {
window.addEventListener("resize", changeWindowSize);
return () => {
window.removeEventListener("resize", changeWindowSize);
};
}, []);
return windowSize;
}
/* Para usar o hook, basta importá-lo para onde for necessário, chamá-lo e usar a largura sempre que quisermos ocultar ou mostrar determinados elementos, como em um componente Header. */
// components/Header.js
import React from "react";
import useWindowSize from "../utils/useWindowSize";
function Header() {
const { width } = useWindowSize();
return (
<div>
{/* visível somente quando a janela for maior que 500px */}
{width > 500 && (
<>
Maior que 500px!
</>
)}
{/* visível em qualquer tamanho de janela */}
<p>Estou sempre visível</p>
</div>
);
}
Regras dos hooks
Há duas regras essenciais para o uso de hooks do React que não podem ser violadas para que funcionem corretamente:
- Os hooks só podem ser usados em componentes de função (não em funções JavaScript simples ou componentes de classe)
- Os hooks só podem ser chamados na parte superior dos componentes (não podem estar em condicionais, laços ou funções aninhadas)
Conclusão
Há outros conceitos interessantes que você pode aprender, mas se você se comprometer a aprender os conceitos abordados nesta ficha informativa, terá uma ótima compreensão das partes mais importantes e poderosas da biblioteca do React.