O React, uma biblioteca JavaScript popular para construir interfaces de usuário, evoluiu significativamente ao longo do tempo. Compreender seu ciclo de vida e a introdução dos Hooks é fundamental para desenvolver aplicações eficientes e modernas.
Evolução do React e Conceitos Fundamentais
Antes da versão 16.8.0, o desenvolvimento em React era predominantemente baseado em componentes de classe. A introdução dos Hooks nessa versão marcou uma mudança significativa, permitindo o uso de estado e outros recursos do React em componentes funcionais. As versões 17 e 18 consolidaram essa abordagem, com a versão 18 focando em componentes funcionais e Hooks, e abandonando o suporte a navegadores mais antigos como o Internet Explorer 11. Para garantir compatibilidade em ambientes que carecem de recursos modernos de navegador, pode-se considerar a inclusão de um polyfill global.
Polyfills e Shims
Um Polyfill é um código que implementa funcionalidades nativas que não são suportadas pelo ambiente de execução atual. Um Shim, por outro lado, é uma biblioteca que introduz uma nova API em um ambiente legado, utilizando apenas os recursos existentes nesse ambiente.
Ciclo de Vida dos Componentes de Classe
O ciclo de vida de um componente de classe no React pode ser dividido em três fases principais:
Fase de Inicialização (Pré-React 14)
Iniciada pela chamada de ReactDOM.render():
constructor(): Inicializa o estado do componente e vincula métodos.componentWillMount()(Obsoleto): Executado antes da montagem inicial.render(): Retorna a estrutura UI do componente. Deve ser uma função pura.componentDidMount(): Executado após o componente ser montado no DOM. Ideal para chamadas de API e manipulação do DOM.
Fase de Atualização (Pré-React 14)
Iniciada por this.setState() dentro do componente ou por uma nova renderização do componente pai:
componentWillReceiveProps()(Obsoleto): Chamado antes de um componente montado receber novas props.shouldComponentUpdate(): Determina se o componente deve ser re-renderizado. Permite otimização de performance.componentWillUpdate()(Obsoleto): Executado antes da atualização visual ocorrer.render(): Executado novamente para refletir as mudanças.componentDidUpdate(): Executado após a atualização visual do componente. Útil para reagir a mudanças em props ou estado.
Fase de Desmontagem (Pré-React 14)
Iniciada por ReactDOM.unmountComponentAtNode():
componentWillUnmount(): Executado antes do componente ser desmontado. Essencial para limpeza (cancelar timers, remover listeners, etc.).
Ciclo de Vida em Versões Posteriores (React 14+)
A partir do React 14, o ciclo de vida foi atualizado, com a introdução de novos métodos e a depreciação de outros.
Fase de Inicialização
constructor()getDerivedStateFromProps(nextProps, prevState): Método estático que permite atualizar o estado interno com base nas props recebidas. SubstituicomponentWillReceiveProps.render()componentDidMount()
Fase de Atualização
getDerivedStateFromProps(nextProps, prevState)shouldComponentUpdate(nextProps, nextState)render()getSnapshotBeforeUpdate(prevProps, prevState): Executado logo antes das alterações serem refletidas no DOM. Permite capturar informações do DOM (como posição de scroll) antes da atualização.componentDidUpdate(prevProps, prevState, snapshot): Recebe o valor retornado porgetSnapshotBeforeUpdatecomo terceiro argumento (snapshot).
Fase de Desmontagem
componentWillUnmount()
Hooks: A Nova Era dos Componentes Funcionais
Hooks permitem usar estado e outros recursos do React em componentes funcionais, tornando o código mais conciso e legível.
useState
Permite adicionar estado a componentes funcionais. Retorna um par: o valor do estado atual e uma função para atualizá-lo.
{`
import { useState } from 'react';
function Contador() {
const [contador, setContador] = useState(0);
return (
<div>
<p>Você clicou {contador} vezes</p>
<button onclick="{()"> setContador(contador + 1)}>
Clique aqui
</button>
</div>
);
}
`}
useEffect
Permite executar efeitos colaterais em componentes funcionais. É equivalente a combinar componentDidMount, componentDidUpdate e componentWillUnmount.
{`
import React, { useState, useEffect } from 'react';
function ExemploEfeito() {
const [dados, setDados] = useState(null);
// Executa após a montagem e sempre que 'idUsuario' mudar
useEffect(() => {
fetchDadosUsuario(idUsuario).then(dados => setDados(dados));
// Função de limpeza, executada antes da desmontagem ou re-execução do efeito
return () => {
// Limpar recursos, como cancelamento de requisições
};
}, [idUsuario]); // Array de dependências
// Executa apenas uma vez após a montagem (sem dependências)
useEffect(() => {
console.log("Componente montado!");
const subscription = pubsub.subscribe('evento', minhaFuncao);
return () => {
pubsub.unsubscribe(subscription); // Limpeza
};
}, []);
return <div>{dados ? dados.nome : 'Carregando...'}</div>;
}
`}
useRef
Retorna um objeto mutável cuja propriedade .current é inicializada com o valor passado. É útil para acessar referências de elementos DOM ou para persistir valores mutáveis entre renderizações sem causar re-renderizações.
{`
import React, { useState, useRef } from 'react';
function Timer() {
const [tempo, setTempo] = useState(0);
const intervalRef = useRef(null);
const iniciarTimer = () => {
intervalRef.current = setInterval(() => {
setTempo(prevTempo => prevTempo + 1);
}, 1000);
};
const pararTimer = () => {
clearInterval(intervalRef.current);
};
// Exemplo de acesso ao DOM
const inputRef = useRef(null);
const focusInput = () => {
inputRef.current.focus();
};
return (
<div>
<p>Tempo: {tempo}s</p>
<button onclick="{iniciarTimer}">Iniciar</button>
<button onclick="{pararTimer}">Parar</button>
<input ref="{inputRef}" type="text"></input>
<button onclick="{focusInput}">Focar Input</button>
</div>
);
}
`}
useContext
Permite consumir um valor de um React Context. Facilita o compartilhamento de dados entre componentes sem a necessidade de passar props manualmente em todos os níveis.
useCallback e useMemo
São Hooks de otimização de performance.
useCallback: Retorna uma versão memorizada da callback, útil para evitar re-renderizações desnecessárias de componentes filhos que dependem dessa callback.useMemo: Retorna um valor memorizado. Útil para otimizar cálculos custosos, recalculando o valor apenas quando uma de suas dependências muda.
{`
import React, { useState, useCallback, useMemo } from 'react';
function OtimizadoComponente({ items }) {
const [filtro, setFiltro] = useState('');
// useMemo para calcular o resultado de forma otimizada
const itensFiltrados = useMemo(() => {
console.log('Calculando itens filtrados...');
return items.filter(item => item.includes(filtro));
}, [items, filtro]);
// useCallback para memorizar a função de callback
const handleFiltroChange = useCallback((event) => {
setFiltro(event.target.value);
}, []); // Sem dependências, a função nunca muda
return (
<div>
<input onchange="{handleFiltroChange}" placeholder="Filtrar..." type="text" value="{filtro}"></input>
<ul>
{itensFiltrados.map((item, index) => (
<li key="{index}">{item}</li>
))}
</ul>
</div>
);
}
`}
Gerenciamento de Estado e Ferramentas
Para aplicações maiores, o gerenciamento de estado se torna crucial. Ferramentas como Redux, Redux Saga, Dva e Umi oferecem soluções robustas.
Redux
Segue o padrão Flux com um único store centralizado, actions e reducers para gerenciar o estado de forma previsível e com fluxo de dados unidirecional.
Redux Saga
Um middleware para Redux que facilita o gerenciamento de efeitos colaterais complexos e assíncronos, utilizando Generators.
Dva
Um framework de gerenciamento de estado baseado em Redux, Redux Saga e Umi Router, que simplifica a estrutura do projeto com o conceito de Models.
UmiJS
Um framwork de desenvolvimento de aplicações React, conhecido por sua extensibilidade e convenções, que integra ferramentas como Dva e Ant Design.
Comunicação com o Servidor (Requisições HTTP)
A comunicação entre o cliente e o servidor é essencial. Diversas abordagens existem:
XMLHttpRequest (XHR)
A API nativa do JavaScript para realizar requisições HTTP. Foi a base para o desenvolvimento do Ajax.
jQuery Ajax
Uma abstração sobre o XHR que simplifica a realização de requisições Ajax, embora dependa da biblioteca jQuery.
Axios
Uma biblioteca popular baseada em Promises para realizar requisições HTTP. Oferece recursos como interceptores, cancelamento de requisições e tratamento de erros.
{`
import axios from 'axios';
async function buscarDados() {
try {
const response = await axios.get('/api/dados', {
params: { id: 123 },
headers: { 'Authorization': 'Bearer token' }
});
console.log(response.data);
} catch (error) {
console.error('Erro ao buscar dados:', error);
}
}
`}
Fetch API
A API moderna e baseada em Promises para realizar requisições de rede. É mais flexível que o XHR, mas requer tratamento adicional para certos cenários (como rejeição de status de erro e timeouts).
{`
async function enviarDados() {
try {
const response = await fetch('/api/enviar', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ nome: 'Exemplo', valor: 100 }),
});
if (!response.ok) {
throw new Error(`Erro HTTP: ${response.status}`);
}
const resultado = await response.json();
console.log(resultado);
} catch (error) {
console.error('Erro ao enviar dados:', error);
}
}
`}
Estratégias para Lidar com Cross-Origin Requests (CORS)
A política de mesma origem (Same-Origin Policy) restringe requisições entre domínios diferentes. Soluções comuns incluem:
- JSONP: Uma técnica legada que utiliza tags
<script>para obter dados de outro domínio. Suporta apenas requisições GET. - CORS (Cross-Origin Resource Sharing): Um padrão W3C que permite aos servidores especificar quais origens têm permissão para acessar seus recursos. A configuração é feita no lado do servidor.
Ao configurar o servidor (por exemplo, com Express), você pode adicionar cabeçalhos apropriados:
{`
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*'); // Ou um domínio específico
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
next();
});
`}
Estrutura de um Projeto React com UmiJS
A criação e configuração de um projeto UmiJS envolve:
- Instalação do Node.js.
- Criação do projeto usando o CLI do Umi:
npx @umijs/create-umi-app nome-do-projeto. - Instalação das dependências:
npm install. - Execução do projeto:
npm run start. - Configuração de rotas no arquivo
.umirc.tsou em um arquivo dedicado (router.tsx). Umi suporta rotas configuradas e rotas por convenção (baseadas na estrutura de arquivos).
A navegação entre rotas pode ser feita usando o hook history do Umi.
{`
import { history } from 'umi';
// Navegar para uma rota
history.push('/pagina-destino');
// Navegar com parâmetros de query
history.push({
pathname: '/pagina-destino',
query: { id: '123', nome: 'Teste' },
});
`}
Modelos (.tsx dentro de src/models) são usados para gerenciar estado com Dva, definindo state, reducers (para atualizações síncronas) e effects (para lógica assíncrona).
A conexão entre componentes e modelos é feita com a função connect, similar ao React-Redux, permitindo acessar o estado e despachar actions.