Partilhar: WhatsApp
aulify · Sebenta
UC · Unidade de Competência · UC00621

Sebenta · React (UC00621)

Framework JavaScript para interfaces modernas
25h · 2.25 pontos crédito Curso: T. Desenv. Software ↗ Referencial oficial SNQ
Índice

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:

  1. Criar um novo elemento <li> com document.createElement.
  2. Definir o seu texto.
  3. Adicionar event listeners (clicar para completar, clicar para remover).
  4. 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

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: - classclassName (porque class é palavra reservada em JS). - forhtmlFor (idem). - Eventos em camelCase: onclickonClick, onchangeonChange. - 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>
  );
}

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>;
}
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).

<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

Estado

Performance

Acessibilidade

15. Próximos passos

Depois de dominares estes fundamentos, explora:

Glossário

Recursos

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.