Sebenta · React (UC00621)
- Introdução
- 1. Porquê React?
- 2. Setup do ambiente
- 3. JSX — a sintaxe do React
- 4. Componentes
- 5. Props
- 6. Estado com useState
- 7. Eventos e formulários
- 8. useEffect — side effects
- 9. Listas e keys
- 10. Routing — React Router
- 11. Estado partilhado e lifting state up
- 12. Estilização
- 13. Deploy
- 14. Boas práticas
- 15. Próximos passos
- Glossário
- Recursos
Introdução
React é a biblioteca JavaScript mais popular para construir interfaces de utilizador. Criada pelo Facebook em 2013, hoje suporta sites como Netflix, Airbnb, Instagram, Discord e milhares de outros. Aprender React é o passo natural depois de dominares HTML/CSS/JavaScript — e é uma das skills mais procuradas no mercado de trabalho frontend.
Esta sebenta cobre os fundamentos práticos: componentes, props, estado, hooks, eventos, listas e routing. No fim, vais ser capaz de construir aplicações React do zero e fazer deploy público.
1. Porquê React?
O problema do JavaScript vanilla
Imagina que tens uma lista de tarefas. Em JavaScript puro, para adicionar uma nova tarefa, terias que:
- Criar um novo elemento
<li>comdocument.createElement. - Definir o seu texto.
- Adicionar event listeners (clicar para completar, clicar para remover).
- Anexar ao
<ul>correcto.
Para 10 tarefas, isto multiplica por 10. Para uma app com formulários, modais e listas, o código rapidamente fica num emaranhado impossível de manter.
A solução React
React inverte o modelo. Em vez de manipular o DOM manualmente, descreves como a UI deve parecer dado o estado actual, e React trata de actualizar o DOM eficientemente.
function TodoList({ todos }) {
return (
<ul>
{todos.map(t => <li key={t.id}>{t.texto}</li>)}
</ul>
);
}
Quando todos muda, React re-renderiza automaticamente apenas o que mudou.
Componentes reutilizáveis
Cada parte da UI é um componente: bloco reutilizável que combina HTML (JSX), CSS e lógica JavaScript. Um botão, um card, um formulário — cada um é um componente. Compõe-se como peças LEGO.
<App>
<Header />
<Main>
<Sidebar />
<Content>
<Article />
<Article />
</Content>
</Main>
<Footer />
</App>
Quando usar React
React faz sentido quando: - A app tem interactividade significativa (não é um site estático). - Há estado complexo (formulários multi-step, dashboards, chats). - Queres componentização para reuso entre páginas. - O projecto vai crescer (vale a pena o setup inicial).
Para sites simples (landing page estática, blog) é exagero — usa HTML/CSS puro ou um SSG (Astro, Eleventy).
2. Setup do ambiente
Pré-requisitos
- Node.js 18+ instalado (verifica com
node --version). - npm ou yarn (vem com Node).
- Editor: VSCode com extensão ES7+ React/Redux/React-Native snippets.
- Browser: Chrome ou Firefox com React Developer Tools instalado.
Criar projecto com Vite
Vite é o bundler moderno recomendado (substitui o antigo Create React App).
npm create vite@latest minha-app -- --template react
cd minha-app
npm install
npm run dev
Abre http://localhost:5173 — vês a página inicial do Vite. Hot Module Replacement (HMR) funciona automaticamente: sempre que guardas um ficheiro, o browser actualiza sem perder estado.
Estrutura do projecto
minha-app/
├── public/ # Assets estáticos (favicon, imagens)
├── src/
│ ├── App.jsx # Componente principal
│ ├── main.jsx # Entry point — monta App no DOM
│ ├── App.css # CSS do App
│ ├── index.css # CSS global
│ └── assets/ # Imagens, ícones do app
├── index.html # HTML inicial (só com <div id="root"></div>)
├── package.json # Dependências
├── vite.config.js # Config do Vite
└── node_modules/ # (gerado por npm install)
Comandos essenciais
npm run dev # Servidor desenvolvimento
npm run build # Build de produção em dist/
npm run preview # Pré-visualizar build localmente
3. JSX — a sintaxe do React
O que é JSX
JSX é uma extensão de JavaScript que permite escrever marcação parecida com HTML directamente no código JS. Não é HTML — é JavaScript que será compilado.
const elemento = <h1>Olá mundo</h1>;
Por baixo, isto compila para:
const elemento = React.createElement("h1", null, "Olá mundo");
Expressões em JSX
Tudo entre {} é JavaScript:
const nome = "Ana";
const saudacao = <h1>Olá, {nome}!</h1>;
const numeros = [1, 2, 3];
const lista = <p>Total: {numeros.length}</p>;
const isLogged = true;
const status = <p>{isLogged ? "Online" : "Offline"}</p>;
Atributos JSX
Atributos em camelCase, valores entre {} se forem expressões:
<div className="card" onClick={handleClick}>
<img src={imageUrl} alt={description} />
<input type="text" value={inputValue} onChange={handleChange} />
</div>
Diferenças do HTML:
- class → className (porque class é palavra reservada em JS).
- for → htmlFor (idem).
- Eventos em camelCase: onclick → onClick, onchange → onChange.
- Self-closing obrigatório: <br />, <img />, <input />.
Regras essenciais
1. Um único elemento root por return:
// ❌ ERRO
return (
<h1>Título</h1>
<p>Texto</p>
);
// ✅ Com fragment
return (
<>
<h1>Título</h1>
<p>Texto</p>
</>
);
// ✅ Ou com div
return (
<div>
<h1>Título</h1>
<p>Texto</p>
</div>
);
2. Comentários em JSX:
return (
<div>
{/* Isto é um comentário */}
<h1>Olá</h1>
</div>
);
4. Componentes
Componente funcional
A forma moderna (e recomendada) de criar componentes é como funções que retornam JSX:
function Saudacao() {
return <h1>Olá mundo!</h1>;
}
// Ou arrow function
const Saudacao = () => <h1>Olá mundo!</h1>;
Convenções obrigatórias:
- Nome em PascalCase (MeuComponente, não meuComponente).
- Retorna sempre JSX (ou null).
- Ficheiro com extensão .jsx.
Usar componentes
Componentes são tags JSX que começam com letra maiúscula:
function App() {
return (
<div>
<Saudacao />
<Saudacao />
<Saudacao />
</div>
);
}
Organização em ficheiros
Boas práticas:
src/
├── App.jsx
├── components/
│ ├── Header.jsx
│ ├── Footer.jsx
│ ├── Button.jsx
│ └── Card.jsx
└── pages/
├── Home.jsx
└── About.jsx
Cada componente num ficheiro próprio, exportado:
// components/Button.jsx
function Button({ onClick, children }) {
return <button onClick={onClick}>{children}</button>;
}
export default Button;
// App.jsx
import Button from "./components/Button";
function App() {
return <Button onClick={() => alert("Click!")}>Clica</Button>;
}
5. Props
Passar dados de pai para filho
Props (properties) são argumentos que componentes recebem do pai. São o equivalente a parâmetros de função.
function Card({ titulo, descricao, imagem }) {
return (
<div className="card">
<img src={imagem} alt={titulo} />
<h2>{titulo}</h2>
<p>{descricao}</p>
</div>
);
}
// Uso
<Card
titulo="React"
descricao="Framework JavaScript"
imagem="/react-logo.png"
/>
Props com destructuring
Recomendado:
// Sem destructuring
function Card(props) {
return <h2>{props.titulo}</h2>;
}
// Com destructuring (preferido)
function Card({ titulo }) {
return <h2>{titulo}</h2>;
}
Default values
function Button({ texto = "Clica", cor = "blue" }) {
return <button style={{ background: cor }}>{texto}</button>;
}
<Button /> // "Clica" azul
<Button texto="Enviar" /> // "Enviar" azul
<Button cor="red" /> // "Clica" vermelho
Props são imutáveis
Nunca modificar props dentro do componente:
// ❌ ERRO
function Card({ titulo }) {
titulo = titulo.toUpperCase(); // não fazer
return <h2>{titulo}</h2>;
}
// ✅ Criar nova variável
function Card({ titulo }) {
const tituloMaiusculo = titulo.toUpperCase();
return <h2>{tituloMaiusculo}</h2>;
}
Children
children é uma prop especial — o que vai entre as tags:
function Card({ children }) {
return <div className="card">{children}</div>;
}
// Uso
<Card>
<h2>Título</h2>
<p>Texto qualquer</p>
<Button>OK</Button>
</Card>
Útil para componentes wrapper (modals, sidebars, layouts).
6. Estado com useState
O problema sem estado
function Counter() {
let count = 0;
return (
<div>
<p>{count}</p>
<button onClick={() => count++}>+1</button>
</div>
);
}
Isto não funciona. count++ actualiza a variável, mas o componente nunca re-renderiza. Para isso precisas de estado.
useState
useState é um hook que cria estado local num componente:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
useState(0)cria estado com valor inicial 0.- Retorna array com 2 elementos:
[valor, setter]. - Chamar o setter triggers re-render do componente.
Múltiplos estados
Podes ter quantos useState quiseres:
function Form() {
const [nome, setNome] = useState("");
const [email, setEmail] = useState("");
const [idade, setIdade] = useState(0);
return (
<div>
<input value={nome} onChange={e => setNome(e.target.value)} />
<input value={email} onChange={e => setEmail(e.target.value)} />
<input value={idade} onChange={e => setIdade(Number(e.target.value))} />
</div>
);
}
Estado com objectos
const [user, setUser] = useState({
nome: "",
email: "",
idade: 0,
});
// Update — IMPORTANTE: criar novo objecto, não mutar
setUser({
...user, // spread do objecto existente
nome: "Ana" // sobrescreve nome
});
// ❌ ERRO — mutação directa
user.nome = "Ana";
setUser(user); // React não detecta mudança
Updates baseados em estado anterior
Quando o novo estado depende do anterior, usa a forma com callback:
// ❌ Pode causar bugs com múltiplos updates
setCount(count + 1);
// ✅ Sempre correcto
setCount(prev => prev + 1);
Especialmente importante dentro de loops, timers, ou handlers assíncronos.
7. Eventos e formulários
Event handlers
React usa eventos sintéticos com camelCase:
function Button() {
const handleClick = (e) => {
console.log("Clicado!", e);
};
return <button onClick={handleClick}>Clica</button>;
}
Eventos comuns:
- onClick — clique.
- onChange — input mudou.
- onSubmit — formulário submetido.
- onInput — input em tempo real.
- onFocus / onBlur — foco entrar/sair.
- onKeyDown / onKeyUp — teclas.
- onMouseEnter / onMouseLeave — hover.
preventDefault
Em formulários, é essencial:
function Form() {
const handleSubmit = (e) => {
e.preventDefault(); // não recarregar página
console.log("Submitido!");
};
return (
<form onSubmit={handleSubmit}>
<button type="submit">Enviar</button>
</form>
);
}
Controlled inputs
Input cujo valor é controlado pelo estado React:
function Form() {
const [nome, setNome] = useState("");
return (
<input
type="text"
value={nome}
onChange={(e) => setNome(e.target.value)}
/>
);
}
Fluxo:
1. User escreve no input.
2. onChange dispara com o novo valor.
3. setNome actualiza estado.
4. React re-renderiza com novo value.
Formulário completo
function Contacto() {
const [form, setForm] = useState({
nome: "",
email: "",
mensagem: ""
});
const handleChange = (e) => {
setForm({
...form,
[e.target.name]: e.target.value
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log("Enviar:", form);
// Aqui poderias fazer fetch para API
setForm({ nome: "", email: "", mensagem: "" });
};
return (
<form onSubmit={handleSubmit}>
<input name="nome" value={form.nome} onChange={handleChange} />
<input name="email" value={form.email} onChange={handleChange} />
<textarea name="mensagem" value={form.mensagem} onChange={handleChange} />
<button type="submit">Enviar</button>
</form>
);
}
Truque do [e.target.name]: o input com name="email" vai actualizar a chave email do estado.
8. useEffect — side effects
O que são side effects
Tudo o que afecta algo fora do componente: - Fetch de API. - Timers (setInterval, setTimeout). - Subscriptions (WebSocket, event listeners). - Manipular DOM directamente. - Logs.
useEffect básico
import { useState, useEffect } from "react";
function Component() {
useEffect(() => {
console.log("Componente renderizou");
});
return <div>Hello</div>;
}
Sem array de dependências, corre em todos os renders.
Array de dependências
// Corre só uma vez (mount)
useEffect(() => {
console.log("Mount");
}, []);
// Corre quando 'count' muda
useEffect(() => {
console.log("Count mudou:", count);
}, [count]);
// Corre quando qualquer um muda
useEffect(() => {
// ...
}, [count, name, email]);
Cleanup
Para subscriptions e timers, retorna uma função de cleanup:
useEffect(() => {
const timer = setInterval(() => {
setTime(new Date());
}, 1000);
// Cleanup ao unmount ou antes de re-correr
return () => {
clearInterval(timer);
};
}, []);
Fetch de dados
Padrão comum:
function Users() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("https://api.github.com/users")
.then(r => {
if (!r.ok) throw new Error("Erro " + r.status);
return r.json();
})
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err.message);
setLoading(false);
});
}, []);
if (loading) return <p>A carregar...</p>;
if (error) return <p>Erro: {error}</p>;
return (
<ul>
{users.map(u => (
<li key={u.id}>{u.login}</li>
))}
</ul>
);
}
Versão com async/await
useEffect(() => {
async function load() {
try {
const r = await fetch("https://api.github.com/users");
const data = await r.json();
setUsers(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
load();
}, []);
(Não podes tornar a função do useEffect directamente async — daí a função interna.)
9. Listas e keys
Renderizar arrays
.map() é o padrão:
const frutas = ["maçã", "banana", "uva"];
function Lista() {
return (
<ul>
{frutas.map((fruta, index) => (
<li key={index}>{fruta}</li>
))}
</ul>
);
}
Importância das keys
React usa key para identificar cada item da lista. Sem key, React não consegue saber o que mudou eficientemente.
Regras: - Key deve ser única entre irmãos. - Key deve ser estável (não muda entre renders). - Preferir ID real ao índice do array.
// ❌ Mal — index muda quando reordenas
{todos.map((t, i) => <li key={i}>{t.texto}</li>)}
// ✅ Bom — ID estável
{todos.map(t => <li key={t.id}>{t.texto}</li>)}
Adicionar items
const [todos, setTodos] = useState([]);
const [input, setInput] = useState("");
const adicionar = () => {
setTodos([
...todos,
{ id: Date.now(), texto: input, feito: false }
]);
setInput("");
};
Remover items
const remover = (id) => {
setTodos(todos.filter(t => t.id !== id));
};
Actualizar items
const toggle = (id) => {
setTodos(todos.map(t =>
t.id === id ? { ...t, feito: !t.feito } : t
));
};
Componente TodoList completo
function TodoList() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState("");
const adicionar = (e) => {
e.preventDefault();
if (!input.trim()) return;
setTodos([...todos, { id: Date.now(), texto: input, feito: false }]);
setInput("");
};
const toggle = (id) => {
setTodos(todos.map(t =>
t.id === id ? { ...t, feito: !t.feito } : t
));
};
const remover = (id) => {
setTodos(todos.filter(t => t.id !== id));
};
return (
<div>
<form onSubmit={adicionar}>
<input value={input} onChange={e => setInput(e.target.value)} />
<button type="submit">Adicionar</button>
</form>
<ul>
{todos.map(t => (
<li key={t.id}>
<input
type="checkbox"
checked={t.feito}
onChange={() => toggle(t.id)}
/>
<span style={{ textDecoration: t.feito ? "line-through" : "none" }}>
{t.texto}
</span>
<button onClick={() => remover(t.id)}>X</button>
</li>
))}
</ul>
</div>
);
}
10. Routing — React Router
Instalação
npm install react-router-dom
Setup básico
import { BrowserRouter, Routes, Route, Link } from "react-router-dom";
function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">Home</Link>
<Link to="/sobre">Sobre</Link>
<Link to="/contacto">Contacto</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/sobre" element={<Sobre />} />
<Route path="/contacto" element={<Contacto />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
Rotas dinâmicas
<Route path="/user/:id" element={<UserDetail />} />
E para aceder ao parâmetro:
import { useParams } from "react-router-dom";
function UserDetail() {
const { id } = useParams();
return <h1>User {id}</h1>;
}
Navegação programática
import { useNavigate } from "react-router-dom";
function LoginButton() {
const navigate = useNavigate();
const handleLogin = () => {
// ... lógica login
navigate("/dashboard");
};
return <button onClick={handleLogin}>Login</button>;
}
11. Estado partilhado e lifting state up
O problema
Quando dois componentes irmãos precisam do mesmo estado:
App
├── Counter (precisa de count)
└── Display (precisa de count)
Solução: subir o estado para o pai comum.
function App() {
const [count, setCount] = useState(0);
return (
<div>
<Counter count={count} setCount={setCount} />
<Display count={count} />
</div>
);
}
function Counter({ count, setCount }) {
return <button onClick={() => setCount(count + 1)}>+1</button>;
}
function Display({ count }) {
return <p>Total: {count}</p>;
}
Quando passa de aninhado, considera Context
Se precisas de passar props 5 níveis abaixo (prop drilling), considera useContext ou state management (Zustand, Redux).
12. Estilização
CSS tradicional
import "./Button.css";
function Button() {
return <button className="btn-primary">OK</button>;
}
Inline styles
<div style={{ color: "red", fontSize: 18 }}>Texto</div>
Útil para valores dinâmicos:
<div style={{ background: isActive ? "green" : "gray" }}>
CSS Modules
/* Button.module.css */
.primary { background: blue; }
import styles from "./Button.module.css";
function Button() {
return <button className={styles.primary}>OK</button>;
}
Cada classe fica isolada (sem conflitos globais).
Tailwind CSS (popular)
<button className="bg-blue-500 hover:bg-blue-700 text-white px-4 py-2 rounded">
OK
</button>
13. Deploy
Build de produção
npm run build
Cria pasta dist/ com HTML, CSS, JS minificados. Esta pasta pode ser servida por qualquer web server.
Vercel (recomendado)
npm i -g vercel
vercel
Segue os prompts. Em segundos tens URL público. Cada vercel --prod faz redeploy.
Alternativa via dashboard: 1. Push código para GitHub. 2. Importar repo em vercel.com. 3. Cada push → deploy automático.
Netlify
Drag-and-drop da pasta dist/ em netlify.com/drop.
Ou conectar GitHub para deploy contínuo.
GitHub Pages
npm install --save-dev gh-pages
// package.json
"homepage": "https://username.github.io/repo",
"scripts": {
"deploy": "gh-pages -d dist"
}
npm run build && npm run deploy
14. Boas práticas
Componentes
- Pequenos: 1 ficheiro = 1 componente, < 200 linhas idealmente.
- Single Responsibility: cada componente faz uma coisa.
- PascalCase:
MyComponent, nãomyComponent. - Composição > herança.
Estado
- Mínimo necessário — não duplicar dados deriváveis.
- Lifted para o nível certo — não mais alto que precisas.
- Imutável — sempre criar novo objecto/array.
Performance
- Listas grandes: virtualização (react-window).
- Memoização:
useMemo,useCallback,React.memo. - Code splitting:
React.lazy+Suspense. - Imagens optimizadas: WebP, lazy loading.
Acessibilidade
- Semantic JSX: usa
<button>,<nav>,<main>, não<div>para tudo. - alt em imagens.
- labels em inputs.
- aria- quando necessário.
- Keyboard navigation funciona.
15. Próximos passos
Depois de dominares estes fundamentos, explora:
- TypeScript — tipos para React (
useState<string>("")). - Custom hooks — encapsular lógica reutilizável.
- Context API — estado global simples.
- Tanstack Query — gestão de cache de fetch.
- Zustand ou Redux Toolkit — state management.
- Next.js — framework full-stack baseado em React (SSR, routing, API).
- React Native — apps móveis com React.
Glossário
- JSX: extensão de JS para escrever marcação parecida com HTML.
- Componente: função que retorna JSX, reutilizável.
- Props: dados passados pai → filho.
- State: dados internos de um componente que triggers re-render quando mudam.
- Hook: função especial React (começa por
use) que adiciona funcionalidade a componentes. - Virtual DOM: representação em memória da UI, comparada para optimizar updates ao DOM real.
- Re-render: re-execução do componente após mudança de estado/props.
- Mount/Unmount: quando um componente é adicionado/removido do DOM.
- Lifting state up: mover estado para o pai comum quando partilhado entre irmãos.
- Controlled component: input cujo valor é controlado por estado React.
Recursos
- Docs oficiais: react.dev (excelentes, ler do início).
- React Router: reactrouter.com.
- Vite: vitejs.dev.
- React DevTools: extensão browser obrigatória.
- Patterns: patterns.dev.
- Tutorial clássico: react.dev/learn/tutorial-tic-tac-toe.
React é uma das skills mais valiosas no mercado. Os fundamentos são poucos (componentes + props + state + effects) mas a fluência vem com prática. Constrói coisas — pequenas apps, clones de UIs que vês na web. Cada projecto consolida o que aprendeste. Em poucos meses estarás confortável com tudo o que esta sebenta cobre.