image

Unlimited bootcamps and 750+ courses forever

70
%OFF
Article image
Lucas Padroni
Lucas Padroni27/08/2026 23:45
Share
IBM Bob: IA de Nível Empresarial para Desenvolvedores e Tech LeadersRecommended for youIBM Bob: IA de Nível Empresarial para Desenvolvedores e Tech Leaders

Escavando o Front-End 02: Desenvolvendo APIs e avançando até o React.

    image

    const introducao = `
    Seja bem-vindo ao Escavando Front-End — Parte 2.
    
    
    Na primeira parte, foram apresentados diferentes fundamentos relacionados ao desenvolvimento Front-End. Entre os assuntos estudados, foram abordadas as Browser APIs, ou seja, as APIs nativas disponibilizadas pelo navegador, além do JavaScript executado no ambiente do navegador.
    
    Nesta segunda parte, o conteúdo avança para novos conceitos e aprofunda conhecimentos importantes para o desenvolvimento Front-End.
    
    Hoje, serão abordados o desenvolvimento e consumo de APIs, incluindo Fetch, Request, Response, JSON, métodos HTTP como GET, POST, PUT, PATCH e DELETE, Headers, Authentication, Loading, Errors, tratamento de respostas e a integração entre Front-End e Back-End.
    
    Também será estudado o JavaScript moderno e avançado. O principal foco desta parte será o ES6 e os recursos que surgiram a partir dele, como Let e Const, Arrow Functions, Template Literals, Destructuring, Spread e Rest, Modules, Optional Chaining, Nullish Coalescing, Promises, Async/Await, Map, Set e métodos de arrays.
    
    Além disso, serão abordados conceitos de TypeScript, incluindo tipos, interfaces, Type Aliases, Union Types, Intersection Types, Generics, Enums, Type Inference, Type Narrowing, Utility Types e a aplicação do TypeScript no Front-End.
    
    Por fim, também será iniciada a abordagem sobre React, passando por conceitos como componentes, JSX, Props, State, eventos, renderização, condicionais, listas, Keys, componentização, reutilização, Hooks, useState, useEffect, Context e Forms.
    
    Durante o conteúdo, dois projetos serão utilizados como referência prática. Inicialmente, existia a intenção de apresentar um terceiro projeto, mas ele não está mais disponível, provavelmente por ter sido apagado. Por isso, o conteúdo será desenvolvido utilizando os dois projetos restantes, que oferecem referências mais completas para a apresentação dos conceitos.
    
    A proposta desta etapa é avançar além dos fundamentos apresentados anteriormente, entendendo melhor como as aplicações Front-End se comunicam com outras partes de um sistema e como as tecnologias modernas ajudam no desenvolvimento, organização e evolução das aplicações.
    `;
    

    Capítulo 1- Desenvolvendo APIs

    No ecossistema Front-End moderno, a interface raramente opera como um sistema isolado. A responsabilidade da camada visual evoluiu de uma simples exibição de documentos estáticos para a orquestração de dados dinâmicos, integração com serviços de terceiros e gerenciamento de estado assíncrono. O domínio da comunicação via APIs é a fronteira que separa o design de interface da engenharia de software de navegação.

    Fundamentos da Comunicação de Dados

    Trabalhar com APIs no Front-End significa estabelecer uma ponte de comunicação com fontes externas de dados. A interface deixa de ser dona de toda a informação e passa a atuar como uma consumidora de serviços.

    O Fluxo Completo da Comunicação HTTP

    O ciclo de vida de uma interação cliente-servidor segue um fluxo bem determinado:

    1. Ação do Usuário: Evento disparado na camada de apresentação (ex: clique, submissão de formulário).
    2. Requisição HTTP: O cliente constrói e dispara uma solicitação via rede.
    3. Processamento do Servidor: A API recebe a requisição, aplica regras de negócio e consulta a camada de persistência (Banco de Dados).
    4. Resposta HTTP: O servidor retorna os dados solicitados acompanhados de um status code.
    5. Atualização da Interface: O Front-End intercepta a resposta, processa o payload e re-renderiza a View.
    [ Usuário ] ➔ [ Front-End ] ➔ [ Requisição HTTP ] ➔ [ API / Back-End ]
                                                             │
    [ Interface Atualizada ] ◄─ [ Resposta HTTP ] ◄─ [ Banco de Dados ]
    

    Distinção Crucial: Browser APIs vs. Web APIs de Sistemas

    Existe uma diferença fundamental de escopo entre as APIs nativas da plataforma e as APIs de comunicação entre sistemas:

    • Browser APIs: Interfaces fornecidas nativamente pelo runtime do navegador para interação com o ambiente de execução local.
    • Exemplos: localStorage, Geolocation, WebSockets, Clipboard, Fetch API.
    • APIs de Sistemas (Web APIs): Serviços externos acessados via rede HTTP para fornecimento de regras de negócio ou dados centralizados.
    • Exemplos: Endpoints REST/GraphQL próprios, gateways de pagamento, APIs de mapas e autenticação OAuth.

    A Fetch API e o Paradigma Assíncrono

    A Fetch API é a interface padrão do ambiente do navegador para a execução de requisições de rede. Diferente de chamadas síncronas convencionais, o consumo de recursos via rede opera de forma assíncrona baseada em Promises.

    JavaScript

    // Exemplo de requisição assíncrona utilizando async/await
    async function buscarProdutos() {
    const response = await fetch("https://api.exemplo.com/produtos");
    const data = await response.json();
    return data;
    }
    

    Anatomia da Execução Assíncrona

    A execução acima esconde dois momentos assíncronos distintos que devem ser gerenciados:

    1. Conexão e Headers (fetch): O método devolve uma Promise que resolve para um objeto do tipo Response assim que os cabeçalhos HTTP retornam.
    2. Parse do Corpo (response.json()): A extração do payload também é uma operação assíncrona, retornando uma nova Promise com os dados convertidos.

    A Pegadinha do Tratamento de Erros com fetch()

    Um dos equívocos mais comuns em integrações Front-End é assumir que o bloco catch do try/catch capturará erros de resposta HTTP como 404 Not Found ou 500 Internal Server Error.

    Comportamento Nativo: A Promise do fetch() só é rejeitada em caso de falha de rede ou interrupção do pipeline da requisição. Respostas com status de erro HTTP ainda resolvem a Promise com sucesso.

    A validação correta exige a verificação explícita da propriedade response.ok:

    const response = await fetch("https://api.exemplo.com/produtos");
    
    
    // A propriedade .ok retorna true para status HTTP entre 200 e 299
    if (!response.ok) {
    throw new Error(`Falha na requisição. Status HTTP: ${response.status}`);
    }
    
    
    const data = await response.json();
    

    Protocolo HTTP, Métodos e Contratos de Dados

    O sucesso da comunicação depende do cumprimento estrito do Contrato da API — a especificação que define como clientes e servidores trocam mensagens.

    Semântica dos Métodos HTTP

    Os métodos indicam a intenção da operação no servidor:

    MétodoFinalidade SemânticaIdiomática de Payload (Body)GETRecuperação de dados/recursosNão envia corpo na requisiçãoPOSTCriação de um novo recurso no servidorTransmite o novo recurso no bodyPUTSubstituição/Atualização completa do recursoTransmite a entidade completaPATCHAtualização parcial do recursoTransmite apenas os campos alteradosDELETERemoção do recurso especificadoGeralmente sem corpo

    Troca de Dados com JSON e Headers

    A serialização de objetos JavaScript para o formato JSON é o padrão para envio de dados via protocolo HTTP. A comunicação precisa ser contextualizada através dos cabeçalhos (Headers):

    JavaScript

    await fetch("https://api.exemplo.com/users", {
    method: "POST",
    headers: {
      "Content-Type": "application/json", // Informa o formato enviado no body
      "Accept": "application/json"        // Solicita a resposta em formato JSON
    },
    body: JSON.stringify({
      nome: "Lucas",
      email: "lucas@email.com"
    })
    });
    

    Segurança, Autenticação e CORS

    A camada de rede introduz complexidades de segurança que precisam ser tratadas no cliente.

    Autenticação via Headers

    Sistemas baseados em tokens operam injetando credenciais de acesso nos cabeçalhos HTTP da requisição:

    JavaScript

    fetch("https://api.exemplo.com/profile", {
    headers: {
      "Authorization": `Bearer ${accessToken}`
    }
    });
    

    Entendendo CORS (Cross-Origin Resource Sharing)

    O CORS é um mecanismo de segurança implementado pelos navegadores que restringe requisições HTTP cruzadas entre origens diferentes.

    Uma origem é definida pela combinação de três fatores: Protocolo + Domínio + Porta.

    Cliente: http://localhost:5173  ──(Requisição)──►  Servidor: http://localhost:3000
                         ▲
                         │ Bloqueio de Origem Cruzada (CORS)
    
    • CORS não é um erro do Front-End: Na vasta maioria dos casos, o erro ocorre porque o servidor não enviou os cabeçalhos apropriados (ex: Access-Control-Allow-Origin).
    • O Erro do mode: "no-cors": Utilizar essa flag não resolve o problema de CORS. Ela força a requisição a um modo opaco (opaque response), bloqueando o acesso do JavaScript ao corpo da resposta e aos cabeçalhos.

    Gerenciamento de Estados de Interface

    Uma aplicação robusta não trata a comunicação como um evento binário. Toda operação de rede envolve uma máquina de estados:

    [ IDLE ] ──► [ LOADING ] ──┬──► [ SUCCESS ]
                             └──► [ ERROR ]
    

    Arquitetura de Separação de Responsabilidades

    Misturar chamadas de rede, tratamento de erros e atualização da DOM na mesma função gera um código de difícil manutenção. A abordagem recomendada é isolar as camadas:

    JavaScript

    // 1. Camada de Serviço (Comunicação com a API)
    async function fetchProdutosService() {
    const response = await fetch("https://api.exemplo.com/produtos");
    if (!response.ok) {
      throw new Error(`Erro na API: ${response.status}`);
    }
    return response.json();
    }
    
    // 2. Camada de Controle de Fluxo e Interface
    async function handleCarregarProdutos() {
    try {
      ui.setLoading(true);
      const produtos = await fetchProdutosService();
      ui.render(produtos);
    } catch (error) {
      ui.renderError(error.message);
    } finally {
      ui.setLoading(false);
    }
    }
    

    A integração com APIs transforma uma interface estática em um sistema dinâmico. A fluidez da aplicação depende diretamente da maturidade com que o desenvolvedor lida com a assincronia, validações de resposta e tratamento dos cenários de falha.

    Com a teoria e os conceitos estruturais consolidados, a próxima etapa consiste no desenvolvimento prático dessas integrações.

    image

    Após a consolidação teórica sobre o protocolo HTTP e a Fetch API, a compreensão dos fluxos de dados exige a análise de arquiteturas reais. A transição de um modelo pontual de requisição/resposta para um ambiente distribuído exige o uso de servidores intermediários e protocolos bidirecionais.

    Projeto 1: Comunicação P2P Intermediada (Estilo Web Chat)

    A primeira implementação prática consiste em demonstrar a troca de mensagens entre duas instâncias web clientes independentes, utilizando um servidor central como corretor de mensagens (Message Broker).

    Arquitetura de Intermediação Centralizada

    Sistemas de mensagens não conectam diretamente o cliente origem ao cliente destino. A topologia impõe um intermediário para gerenciamento de conexões e roteamento de mensagens.

    [ Cliente Web A ] ──(Requisição/Evento)──► [ Servidor / API Node.js ]
                                                     │
    [ Cliente Web B ] ◄──(Distribuição)─────────┘
    

    Protocolo de Mensagem e Payloads Homogêneos

    Para que o servidor consiga processar e repassar as informações sem corromper o estado das aplicações, a estrutura de dados transmitida deve seguir uma interface rígida:

    JavaScript

    // Payload padrão de mensagem
    const messagePayload = {
    id: "msg_98231",
    sender: "Client_A",
    timestamp: 1724796000,
    type: "text", // Suporta: 'text', 'media_url', 'emoji'
    content: "Olá! Seguem os arquivos do projeto. 😀"
    };
    

    Abstração de Tipos de Dados (Texto, Emojis e Mídias)

    A camada de comunicação trata qualquer informação enviada — de uma simples string com caracteres UTF-8 (emojis) a URLs de referência para arquivos binários — como cargas úteis de dados (payloads).

    • Texto e Emojis: Transmitidos via codificação nativa no formato JSON.
    • Arquivos e Imagens: A boa prática exige que a imagem seja enviada primeiro a um serviço de armazenamento (Bucket/Storage). O payload da mensagem carrega apenas o link do recurso hospedado, otimizando a largura de banda da API.

    image

    image

    Projeto 2: Group Chat — Arquitetura Multicliente em Tempo Real

    Para escalar a comunicação de 2 para $N$ clientes, o padrão HTTP tradicional de requisição/resposta torna-se ineficiente por depender de consultas constantes (polling). O projeto Group Chat estende o modelo centralizado utilizando uma arquitetura baseada em WebSockets.

                         ┌─────────────────┐
                         │  Cliente User1  │
                         └────────┬────────┘
                                  │
    ┌─────────────────┐    ┌────────▼────────┐    ┌─────────────────┐
    │  Cliente User2  │◄───┤ API Server Node │───►│  Cliente User3  │
    └─────────────────┘    └────────┬────────┘    └─────────────────┘
                                  │
                         ┌────────▼────────┐
                         │  Cliente User4  │
                         └─────────────────┘
    

    O Protocolo WebSocket vs. Requisições HTTP

    Enquanto o HTTP estabelece uma nova conexão a cada solicitação e a encerra em seguida, o WebSocket fornece um canal de comunicação bidirecional de full-duplex mantido sobre uma única conexão TCP.

    CaracterísticaProtocolo HTTP (REST)Protocolo WebSocket (WS)ConexãoEfêmera (abre e fecha a cada request)Persistente (stateful)ComunicaçãoUnidirecional (apenas cliente inicia)Bidirecional (cliente e servidor iniciam)OverheadAlto (cabeçalhos enviados em toda request)Baixo (frames de dados reduzidos)Caso de UsoCRUD, carregamento de telas estáticasDashboards em tempo real, chats, jogos

    O Ciclo de Eventos no Servidor (Node.js)

    O servidor atua como um barramento de distribuição (Broadcasting). Quando o User1 dispara uma mensagem, o servidor intercepta o evento e o replica para todos os sockets ativos na mesma sala:

    [ User1 ] ──(emit: 'message')──► [ API WebSocket ] ──(broadcast)──► [ User2, User3, User4 ]
    

    Fluxo de Desenvolvimento e Validação no Terminal

    A construção de APIs requer ciclos curtos de validação antes da integração com o Front-End visual. O terminal atua como ferramenta primária para inspeção do comportamento dos serviços backend.

    [ Escrita do Código ] ➔ [ Execução do Servidor ] ➔ [ Inspeção de Logs no Terminal ] ➔ [ Ajuste de Regra ]
    
    1. Monitoramento de Sockets: Acompanhar eventos de conexão (connection) e desconexão (disconnect) dos clientes através de instâncias de log.
    2. Inspeção de Payload: Validar se a estrutura JSON trafegada condiz com o contrato da API antes que a interface de usuário tente renderizá-la.

    Estrutura do Projeto no GitHub

    O repositório completo contendo o código do servidor Node.js, os scripts de configuração do WebSocket e as interfaces de usuário para múltiplos clientes pode ser consultado no repositório:

    JavaScript

    // Configuração conceitual da arquitetura implementada
    const groupChatSystem = {
    clients: 4,
    runtime: "Node.js",
    protocol: "WebSocket",
    architecture: "Event-Driven Real-Time Messaging"
    };
    

    Com os padrões de comunicação HTTP e WebSocket compreendidos na prática, a próxima etapa avança para o estudo das especificações modernas da linguagem: JavaScript Moderno e ES6+.

    Capítulo 2- JavaScript Moderno e o Desenvolvimento Web

    Falar sobre JavaScript moderno significa, inevitavelmente, abordar o ECMAScript 2015 (ES6) e suas evoluções subsequentes. Antes de analisar a sintaxe de let, const, Arrow Functions, Promises ou Modules, é fundamental compreender o contexto histórico: por que o ES6 representou uma virada de chave no ecossistema da linguagem?

    O Contexto Histórico e a Necessidade de Evolução

    O JavaScript já era a espinha dorsal da interatividade na Web antes de 2015. No entanto, à medida que a Web deixou de abrigar apenas scripts simples de validação de formulários para processar aplicações complexas no cliente (SPAs) e no servidor (Node.js), as limitações do ecossistema original tornaram-se gargalos críticos de produtividade e manutenibilidade.

    A linguagem precisou evoluir para suportar:

    • Sistemas Web de grande porte e plataformas de comunicação;
    • Arquiteturas baseadas em microsserviços e APIs REST/GraphQL;
    • Aplicações em tempo real e ecossistemas complexos de ferramentas de compilação (build tools).

    Limitações Históricas do JavaScript Pré-ES6

    O JavaScript anterior ao ES6 permitiu a construção da Web como a conhecemos, mas apresentava inconsistências arquiteturais significativas:

    1. Escopo imprevisível decorrente do comportamento das variáveis declaradas com var;
    2. Ausência de um sistema nativo de módulos para organização de arquivos;
    3. Complexidade excessiva na manipulação de código assíncrono (Callback Hell);
    4. Sintaxe verbosa para interpolação de strings e manipulação de objetos e arrays.

    Gerenciamento de Escopo: var vs. let e const

    A declaração de variáveis através da palavra-chave var apresentava problemas relacionados ao escopo de função e ao mecanismo de hoisting (elevação).

    Os Problemas da Declaração com var

    O escopo do var não respeita blocos de instrução (if, for, while), apenas funções. Isso permitia a vazão de variáveis para o escopo global ou externo de forma involuntária:

    JavaScript

    // Problema 1: Redeclaração sem erros
    var usuario = "Lucas";
    var usuario = "Maria"; // Permitido silenciosamente
    
    // Problema 2: Ausência de escopo de bloco
    if (true) {
    var mensagem = "Acesso liberado";
    }
    console.log(mensagem); // "Acesso liberado" (Vazamento fora do bloco)
    

    A Solução: Escopo de Bloco com let e const

    O ES6 introduziu let e const, cujo escopo é estritamente limitado ao bloco contêiner ({...}):

    JavaScript

    if (true) {
    const mensagem = "Acesso liberado";
    let contador = 1;
    }
    // console.log(mensagem); // ReferenceError: mensagem is not defined
    

    Mutabilidade de Referência vs. Imutabilidade de Valor

    Existe uma distinção crucial sobre o funcionamento de const: a palavra-chave garante a imutabilidade da atribuição da referência, e não a imutabilidade do valor interno em estruturas complexas (objetos e arrays).

    DeclaraçãoReatribuição de ReferênciaMutabilidade de Propriedades InternasEscopovarPermitidaPermitidaFunção / GloballetPermitidaPermitidaBlococonstImpedidaPermitida (em Objetos/Arrays)BlocoJavaScript

    const usuario = { nome: "Lucas" };
    
    // Alteração de propriedade é PERMITIDA (o objeto é mutável)
    usuario.nome = "Maria";
    
    // Reatribuição de referência GERARÁ ERRO (TypeError)
    // usuario = { nome: "Pedro" };
    

    Arrow Functions e o Contexto Léxico de this

    As Arrow Functions (() => {}) introduziram uma sintaxe concisa para expressões de função, simplificando a escrita de callbacks e métodos encadeados.

    JavaScript

    // Função tradicional
    function somar(a, b) {
    return a + b;
    }
    
    // Arrow Function (Sintaxe completa)
    const somar = (a, b) => {
    return a + b;
    };
    
    // Arrow Function (Retorno implícito)
    const somar = (a, b) => a + b;
    

    O Comportamento do this

    Diferente das funções tradicionais, onde o valor de this é determinado em tempo de execução (binding dinâmico baseado em como a função é chamada), as Arrow Functions não possuem seu próprio contexto de this. Elas herdam o this do contexto léxico em que foram definidas.

    Regra de Aplicação: Arrow Functions não substituem funções tradicionais em todos os cenários. Elas não devem ser utilizadas como métodos diretos de objetos quando houver necessidade de acessar o próprio objeto via this, nem como construtores (new).

    Manipulação Expressiva de Dados: Template Literals, Destructuring e Operators

    Template Literals

    A interpolação de strings através de backticks (`) substituiu a concatenação excessiva com o operador +, permitindo a inserção direta de expressões (${expressão}) e strings multilinha nativas.

    JavaScript

    const cliente = "Lucas";
    const pedidoId = 9482;
    
    // Interpolação concisa
    const notificacao = `Prezado ${cliente}, seu pedido #${pedidoId} foi processado.`;
    

    Destructuring Assignment (Desestruturação)

    Permite extrair dados de objetos ou arrays e atribuí-los a variáveis de forma direta:

    JavaScript

    const configuracao = {
    host: "localhost",
    porta: 8080,
    ambiente: "production"
    };
    
    // Extração de propriedades de objetos
    const { host, porta } = configuracao;
    
    const listaCores = ["#FFF", "#000", "#FF0000"];
    
    // Extração posicional de arrays
    const [corFundo, corTexto] = listaCores;
    

    Spread Operator e Rest Parameters (...)

    A sintaxe de três pontos desempenha papéis opostos dependendo do contexto de aplicação:

    1. Spread Operator (Expansão): Expande os elementos de uma estrutura iterável (array ou objeto) em um novo local.
    2. Rest Parameters (Agrupamento): Agrupa múltiplos argumentos passados a uma função em um único array.

    JavaScript

    // Spread: Imutabilidade e fusão de dados (Cópia Superficial / Shallow Copy)
    const usuarioBase = { nome: "Lucas", nivel: "Admin" };
    const usuarioComStatus = { ...usuarioBase, ativo: true };
    
    // Rest: Parâmetros indefinidos em funções
    function calcularTotal(...valores) {
    return valores.reduce((acc, curr) => acc + curr, 0);
    }
    
    Atenção: O Spread Operator realiza apenas uma cópia superficial (shallow copy). Propriedades que contêm objetos aninhados continuam compartilhando a mesma referência de memória.

    Arquitetura Modular (ES Modules)

    Antes do ES6, a modularização dependia de soluções de terceiros ou Padrões de Projeto específicos (como CommonJS no Node.js ou AMD). O ES6 unificou isso através dos ES Modules (import / export).

    src/
    ├── services/
    │   └── api.js        ──► export function fetchUsers() { ... }
    └── main.js           ──► import { fetchUsers } from './services/api.js';
    

    Esse padrão permitiu a criação de arquiteturas desacopladas, facilitando a aplicação do princípio de responsabilidade única e otimizações de compilação como o Tree Shaking (remoção de código não utilizado).

    Recursos de Segurança de Fluxo e Operadores Modernos

    Optional Chaining (?.)

    O operador de encadeamento opcional interrompe a avaliação e retorna undefined se a referência antes do operador for null ou undefined, evitando a interrupção da execução com exceções do tipo TypeError.

    JavaScript

    const respostaAPI = { usuario: { detalhes: { email: "dev@empresa.com" } } };
    
    // Acesso seguro a propriedades profundas
    const email = respostaAPI?.usuario?.detalhes?.email;
    

    Nullish Coalescing (??)

    Diferente do operador lógico OR (||) — que avalia qualquer valor falsy (false, 0, "", null, undefined) —, o operador ?? retorna o valor à direita apenas se a expressão à esquerda for null ou undefined.

    JavaScript

    const configuracaoUsuario = { limiteDownload: 0 };
    
    // Com || o valor 0 é tratado como falsy, aplicando o padrão (Incorreto para este caso)
    const limiteOR = configuracaoUsuario.limiteDownload || 10; // Resultado: 10
    
    // Com ?? apenas null ou undefined acionam o valor padrão (Correto)
    const limiteNullish = configuracaoUsuario.limiteDownload ?? 10; // Resultado: 0
    

    Assincronismo: De Promises a async/await

    O processamento assíncrono passou por uma evolução fundamental para resolver o problema de legibilidade do Callback Hell.

    Promises

    Uma Promise é um objeto que representa o eventual sucesso ou falha de uma operação assíncrona. Ela possui três estados possíveis:

                    ┌─► Fulfilled (.then())
                    │
    Pending ──────────┤
    (Processando)     │
                    └─► Rejected  (.catch())
    

    Sintaxe async/await

    Introduzida no ES2017 como um syntactic sugar sobre Promises, o async/await permite escrever código assíncrono com uma estrutura visualmente semelhante à síncrona, facilitando o tratamento de erros através dos blocos try/catch.

    JavaScript

    async function carregarDados() {
    try {
      const resposta = await fetch("https://api.exemplo.com/dados");
      const dados = await resposta.json();
      console.log(dados);
    } catch (erro) {
      console.error("Falha no carregamento dos dados:", erro);
    }
    }
    

    Concorrência com Promise.all

    Evita o encadeamento desnecessário de chamadas assíncronas independentes, executando-as em paralelo:

    JavaScript

    // Execução paralela (Mais eficiente quando as chamadas são independentes)
    const [dadosUsuario, dadosPedidos] = await Promise.all([
    buscarUsuario(),
    buscarPedidos()
    ]);
    

    Estruturas de Dados Modernas: Map e Set

    O ES6 adicionou coleções nativas especializadas que complementam o uso de Object e Array.

    • Map: Coleção de pares chave/valor onde as chaves podem ser de qualquer tipo (incluindo objetos ou funções), mantendo a ordem de inserção.
    • Set: Coleção de valores únicos, eliminando duplicidades automaticamente.

    JavaScript

    // Remoção de duplicatas de um Array utilizando Set e Spread
    const numerosComDuplicatas = [1, 2, 2, 3, 4, 4, 5];
    const numerosUnicos = [...new Set(numerosComDuplicatas)]; // [1, 2, 3, 4, 5]
    

    Métodos de Iteração de Arrays

    A manipulação funcional de dados tornou-se o padrão no JavaScript moderno através de métodos imutáveis de iteração:

    JavaScript

    const produtos = [
    { id: 1, nome: "Teclado", preco: 150, ativo: true },
    { id: 2, nome: "Mouse", preco: 80, ativo: false },
    { id: 3, nome: "Monitor", preco: 900, ativo: true }
    ];
    
    // 1. filter: Retorna um novo array filtrado
    const produtosAtivos = produtos.filter(p => p.ativo);
    
    // 2. map: Transforma os elementos em um novo array
    const nomesProdutos = produtos.map(p => p.nome);
    
    // 3. reduce: Acumula os valores em um resultado único
    const valorTotalEstoque = produtos.reduce((total, p) => total + p.preco, 0);
    

    O ES6+ forneceu a infraestrutura necessária para suportar a engenharia de software moderna na linguagem JavaScript. A evolução da sintaxe não busca apenas a brevidade do código, mas sim a redução de efeitos colaterais através da previsibilidade de escopos, modularização nativa e gerenciamento eficiente do assincronismo.

    Entender os motivos por trás da criação de cada recurso é a chave para tomar decisões arquiteturais corretas, evitando o uso inadequado de abstrações modernas.

    Essa reflexão encerra o estudo do ES6 com o pragmatismo técnico correto: tecnologia é resposta a contexto e limitação, não apenas sintaxe nova.

    Como desenvolvedores, a virada de chave mental ao dominar o JavaScript moderno não está em usar a sintaxe mais recente por apelo estético, mas em reconhecer a arquitetura por trás de cada escolha de código:

    • Saber a diferença entre escopo global, de função e de bloco evita bugs silenciosos em tempo de execução.
    • Compreender a diferença entre mutação e reatribuição (especialmente ao trabalhar com const e objetos) é o que garante previsibilidade no fluxo de dados da aplicação.
    • Entender concorrência assíncrona faz a diferença entre travar uma aplicação com await desnecessários encadeados em loop ou resolver tudo de forma eficiente em paralelo com Promise.all.
    • Conhecer os custos de abstração impede que a busca por concisão resulte em códigos ilegíveis ou difíceis de manter para a equipe.

    O ecossistema JavaScript continua evoluindo a cada nova especificação anual do ECMAScript, mas o pilar estabelecido pelo ES6 permanece como a base de todo o desenvolvimento web contemporâneo — dos frameworks no Front-End aos microsserviços no Back-End.

    Problemas Históricos e Soluções do ES6

    Escopo de Variáveis

    • Problema Histórico: O uso do var causava vazamento de escopo para fora de blocos condicionais e de repetição (if, for), mantendo variáveis acessíveis em contextos indevidos.
    • Solução do ES6: Introdução de let e const.
    • Ganho Técnico: As variáveis passam a respeitar estritamente o escopo de bloco (delimitado por {}). O ciclo de vida da variável fica isolado, aumentando a previsibilidade do código.

    Contexto de Execução (this) em Callbacks

    • Problema Histórico: Funções tradicionais passadas como callback (como em setTimeout ou manipuladores de eventos) perdiam a referência do objeto pai, exigindo contornar o problema com atribuições manuais como var self = this ou o uso explicito de .bind(this).
    • Solução do ES6: Introdução das Arrow Functions (() => {}).
    • Ganho Técnico: As Arrow Functions utilizam o this léxico, herdando automaticamente o contexto do escopo onde foram declaradas. Deve-se notar que elas não possuem this próprio, o que desaconselha seu uso como métodos diretos de objetos ou construtores.

    Extração e Acesso a Dados

    • Problema Histórico: Para acessar propriedades de objetos ou elementos de arrays, era necessário declarar variáveis individualmente em múltiplas linhas (var nome = usuario.nome;), gerando código prolixo e repetitivo.
    • Solução do ES6: Introdução do Destructuring Assignment.
    • Ganho Técnico: Permite extrair múltiplos valores de objetos ou arrays em uma única instrução. É amplamente empregado no consumo de payloads de APIs REST. O único cuidado recomendado é evitar desestruturações com muitos níveis de aninhamento para não comprometer a legibilidade.

    Manipulação e Combinação de Estruturas

    • Problema Histórico: A junção de múltiplos arrays ou a cópia de objetos dependia do uso de métodos como .concat() ou laços de repetição manuais.
    • Solução do ES6: Introdução do Spread Operator (...).
    • Ganho Técnico: Permite a expansão direta de elementos de arrays ou propriedades de objetos em novas estruturas. É uma ferramenta central na escrita de código imutável, devendo-se observar que a operação executa apenas uma cópia superficial (shallow copy).

    Conclusão do nosso Módulo de ES6

    A análise comparativa do JavaScript demonstra que o ES6 não trouxe apenas mudanças estéticas na linguagem, mas sim as bases necessárias para suportar aplicações web de alta escala. Compreender os mecanismos fundamentais por trás dessas inovações — como escopo de bloco, ligação léxica e imutabilidade de referência — garante o desenvolvimento de software previsível, seguro e manutenível.

    Com isso, o capítulo sobre JavaScript Moderno (ES6+) está concluído.

    image

    Capítulo 3: TypeScript

    Depois de compreender a evolução do JavaScript moderno, o próximo passo é entender uma tecnologia que se tornou extremamente importante no desenvolvimento de aplicações Web: TypeScript.

    O TypeScript surgiu para lidar principalmente com um dos maiores desafios do JavaScript em aplicações maiores: a dificuldade de identificar determinados erros relacionados aos dados antes da execução do código.

    Neste capítulo, serão abordados os seguintes temas:

    • O que é TypeScript;
    • JavaScript × TypeScript;
    • Tipos;
    • Interfaces;
    • Type Aliases;
    • Union Types;
    • Intersection Types;
    • Generics;
    • Enums;
    • Type Inference;
    • Type Narrowing;
    • Utility Types;
    • Tipagem de Objetos;
    • Tipagem de Funções;
    • TypeScript no Front-end.

    A proposta é estudar cada assunto de forma aprofundada, entendendo o que é, como funciona, qual problema ajuda a resolver e onde pode ser utilizado, sempre com exemplos práticos.

    O que é TypeScript

    TypeScript é uma linguagem baseada em JavaScript que adiciona recursos ao processo de desenvolvimento, principalmente relacionados à tipagem estática. É fundamental compreender que o TypeScript não substitui o JavaScript. O JavaScript continua sendo a base de execução.

    TypeScript
      ↓  (Adiciona recursos de tipagem ao desenvolvimento)
    JavaScript
      ↓  (Transpilação do código)
    JavaScript
      ↓  (Execução final no navegador ou Node.js)
    

    O navegador executa exclusivamente JavaScript. Por isso, o código escrito em TypeScript precisa ser transformado (transpilado) em JavaScript antes de ser utilizado no ambiente de execução.

    A principal vantagem dessa arquitetura está na possibilidade de identificar diversos problemas de tipo e estrutura durante o momento de escrita do código (tempo de desenvolvimento), evitando que cheguem à produção.

    Exemplo Básico de Declaração

    TypeScript

    const nome: string = "Lucas";
    console.log(nome);
    

    Neste exemplo, foi informado explicitamente que a variável nome deve receber exclusivamente um valor do tipo string.

    TypeScript

    const idade: number = 20;
    console.log(idade);
    

    Com essa anotação, o compilador do TypeScript passa a conhecer o tipo esperado para cada valor, permitindo identificar inconsistências com antecedência.

    Validação de Atribuição

    TypeScript

    let usuario: string;
    usuario = "Lucas"; // Correto
    

    Caso tente-se atribuir um tipo incompatível:

    TypeScript

    let usuario: string;
    usuario = 20; // Erro de Compilação
    

    O TypeScript identifica que um valor numérico (number) está sendo atribuído a uma variável que foi definida para receber apenas texto (string). Esse é um dos princípios fundamentais da linguagem.

    JavaScript × TypeScript

    A diferença mais conhecida entre JavaScript e TypeScript está relacionada à forma como cada linguagem lida com os tipos de dados.

    O JavaScript é uma linguagem dinamicamente tipada. Isso significa que o tipo de uma variável é determinado em tempo de execução e pode ser alterado a qualquer momento:

    JavaScript

    // JavaScript tradicional
    let valor = "Olá";
    valor = 10; // Permitido sem restrições
    

    Comparativo de Comportamento

    Em JavaScript:

    JavaScript

    let usuario = "Lucas";
    usuario = 20;
    console.log(usuario); // Imprime 20 sem erros de execução
    

    O JavaScript permite essa alteração dinâmica. Isso não significa necessariamente que o código esteja errado — depende da arquitetura —, mas em sistemas maiores, alterações inesperadas de tipos ao longo do fluxo do sistema geram falhas difíceis de rastrear.

    Em TypeScript:

    TypeScript

    let usuario: string = "Lucas";
    usuario = 20; 
    // Erro: Type 'number' is not assignable to type 'string'.
    

    O TypeScript intercepta a instrução e impede a compilação, pois a regra estipulada para a variável foi violada.

    Comparação na Assinatura de Funções:

    • Em JavaScript:
    • JavaScript

    function somar(a, b) {
    return a + b;
    }
    
    • Em TypeScript:
    • TypeScript

    function somar(a: number, b: number): number {
    return a + b;
    }
    

    A anotação no TypeScript insere um contrato claro sobre os dados:

    • a → deve ser obrigatoriamente um number;
    • b → deve ser obrigatoriamente um number;
    • retorno → a função garante retornar um number.

    Tipos Primitivos

    Os tipos permitem informar a categoria de dado que uma variável representa. Os tipos primitivos fundamentais são:

    • string: Sequências de caracteres (textos);
    • number: Valores numéricos (inteiros ou flutuantes);
    • boolean: Valores lógicos (true ou false);
    • null: Ausência intencional de valor;
    • undefined: Valor não atribuído ou variável não inicializada.

    TypeScript

    const nome: string = "Lucas";
    const idade: number = 20;
    const ativo: boolean = true;
    

    TypeScript

    let produto: string = "Notebook";
    let preco: number = 2500;
    let disponivel: boolean = true;
    

    Essa explicitação facilita a leitura técnica do código e garante que operações inválidas (como tentar executar métodos de string em um valor booleano) sejam bloqueadas imediatamente.

    Interfaces

    Interfaces são utilizadas para definir a estrutura esperada (contrato) de um objeto.

    TypeScript

    interface Usuario {
    nome: string;
    idade: number;
    }
    
    const usuario: Usuario = {
    nome: "Lucas",
    idade: 20
    };
    

    O objeto usuario fica obrigado a implementar exatamente as propriedades definidas pela interface Usuario.

    TypeScript

    interface Produto {
    nome: string;
    preco: number;
    disponivel: boolean;
    }
    
    const produto: Produto = {
    nome: "Teclado",
    preco: 150,
    disponivel: true
    };
    

    Interfaces ajudam a manter a consistência dos dados em aplicações que manipulam respostas de APIs REST ou persistem informações em bancos de dados.

    Type Aliases

    Type Aliases permitem criar uma denominação customizada (um apelido) para um tipo primitivo, uma união ou uma estrutura complexa. Utiliza-se a palavra-chave type.

    TypeScript

    type Nome = string;
    
    const usuario: Nome = "Lucas";
    

    Neste caso, o identificador Nome passa a funcionar como um alias para a primitiva string.

    Estruturas de Objetos com Type Aliases

    TypeScript

    type Usuario = {
    nome: string;
    idade: number;
    };
    
    const usuario: Usuario = {
    nome: "Lucas",
    idade: 20
    };
    

    Type Aliases oferecem grande flexibilidade para reutilização de definidores de tipos em diferentes módulos do projeto.

    Union Types

    Union Types permitem que uma variável ou propriedade aceite mais de um tipo específico de dado. Utiliza-se o operador barra vertical (|).

    TypeScript

    let identificador: string | number;
    
    identificador = "ABC123"; // Válido
    identificador = 123;      // Válido
    

    A variável identificador está apta a receber valores do tipo string OU number.

    Mapeamento de Estados Finitos (Literal Types)

    TypeScript

    type Status = "ativo" | "inativo" | "bloqueado";
    
    let statusUsuario: Status = "ativo"; // Válido
    // statusUsuario = "pendente";      // Erro: valor não permitido
    

    Essa abordagem garante que uma variável receba estritamente um conjunto delimitado de opções, sendo fundamental para o controle de estados em aplicações front-end.

    Intersection Types

    Intersection Types combinam múltiplos tipos em uma única estrutura unificada. Utiliza-se o operador e-comercial (&).

    TypeScript

    type Usuario = {
    nome: string;
    };
    
    type Admin = {
    permissao: string;
    };
    
    type UsuarioAdmin = Usuario & Admin;
    
    const usuario: UsuarioAdmin = {
    nome: "Lucas",
    permissao: "administrador"
    };
    

    O novo tipo UsuarioAdmin exige a presença obrigatória de todas as propriedades declaradas em Usuario e em Admin.

    TypeScript

    type Pessoa = {
    nome: string;
    };
    
    type Profissional = {
    cargo: string;
    };
    
    type Funcionario = Pessoa & Profissional;
    
    const funcionario: Funcionario = {
    nome: "Lucas",
    cargo: "Desenvolvedor"
    };
    

    Intersection Types são extremamente úteis quando existe a necessidade de estender contratos de objetos compostos sem modificar a estrutura original.

    Generics

    Generics oferecem uma forma de criar componentes, funções e interfaces reutilizáveis capazes de trabalhar com múltiplos tipos mantendo a segurança da tipagem (sem recorrer ao tipo any).

    TypeScript

    function retornarValor<T>(valor: T): T {
    return valor;
    }
    
    const nome = retornarValor<string>("Lucas");
    const numero = retornarValor<number>(100);
    

    No código acima, <T> funciona como uma variável de tipo. Quando a função é chamada, o tipo passado substitui o T.

    Manipulação de Arrays genéricos

    TypeScript

    function primeiroItem<T>(lista: T[]): T {
    return lista[0];
    }
    
    const numero = primeiroItem<number>([10, 20, 30]); // Retorna tipo number
    const nome = primeiroItem<string>(["Lucas", "Maria"]); // Retorna tipo string
    

    O mesmo algoritmo é reaproveitado para estruturas de dados completamente distintas mantendo a checagem estática sobre os retornos.

    Enums

    Enums (Enumerações) permitem definir um conjunto nomeado de constantes relativas a um domínio específico.

    TypeScript

    enum Status {
    Ativo,
    Inativo,
    Bloqueado
    }
    
    const statusAtual: Status = Status.Ativo;
    

    Por padrão, os valores de um enum são inteiros numéricos iniciados em 0.

    Enums Baseados em String

    TypeScript

    enum Nivel {
    Baixo = "baixo",
    Medio = "medio",
    Alto = "alto"
    }
    
    const nivelAcesso: Nivel = Nivel.Alto;
    

    Nota Técnica: Embora úteis para grupos fixos de constantes, o uso de Enums deve ser avaliado com critério. Em muitos cenários, o uso de Union Types de strings literais produz um JavaScript transpilado mais leve e direto.

    Type Inference (Inferência de Tipo)

    O TypeScript possui um motor de inferência que deduz automaticamente o tipo de uma variável com base no valor atribuído em sua inicialização, sem a necessidade de anotações manuais redundantes.

    TypeScript

    const nome = "Lucas"; // O TypeScript infere automaticamente: nome: string
    const idade = 20;     // O TypeScript infere automaticamente: idade: number
    const ativo = true;   // O TypeScript infere automaticamente: ativo: boolean
    

    A inferência reduz a verbosidade do código, permitindo reservar a tipagem explícita para argumentos de funções, retornos complexos e declarações sem inicialização imediata.

    Type Narrowing (Afunilamento de Tipos)

    Type Narrowing é o processo onde o TypeScript analisa o fluxo do código e refina o tipo de uma variável de uma união ampla para um tipo mais específico dentro de um bloco condicional.

    TypeScript

    function mostrarValor(valor: string | number) {
    if (typeof valor === "string") {
      // Dentro deste bloco, o TypeScript sabe que 'valor' é estritamente uma string
      console.log(valor.toUpperCase());
    }
    }
    

    Processamento Condicional Seguro

    TypeScript

    function calcular(valor: string | number) {
    if (typeof valor === "number") {
      // Tratado estritamente como number
      return valor * 2;
    }
    // Se passou da condição, é garantido pelo compilador que o tipo é string
    return valor.toUpperCase();
    }
    

    A checagem de tipos em tempo de execução via typeof, instanceof ou checagens de propriedades permite que o compilador garanta a segurança das operações executadas.

    Utility Types

    O TypeScript disponibiliza utilitários genéricos nativos que facilitam a transformação e reaproveitamento de tipos existentes.

    Partial<T>

    Converte todas as propriedades de um tipo T em opcionais (?).

    TypeScript

    interface Usuario {
    nome: string;
    idade: number;
    }
    
    const atualizacao: Partial<Usuario> = {
    nome: "Lucas" // 'idade' torna-se opcional
    };
    

    Pick<T, K>

    Constrói um tipo selecionando apenas o conjunto de propriedades K contidas em T.

    TypeScript

    interface Usuario {
    nome: string;
    email: string;
    idade: number;
    }
    
    type DadosBasicos = Pick<Usuario, "nome" | "email">;
    
    const usuario: DadosBasicos = {
    nome: "Lucas",
    email: "lucas@email.com"
    };
    

    Outros utilitários amplamente empregados incluem:

    • Required<T>: Torna todas as propriedades de T obrigatórias.
    • Omit<T, K>: Remove as propriedades K do tipo T.
    • Readonly<T>: Torna todas as propriedades de T imutáveis (apenas leitura).

    Tipagem de Objetos

    Objetos podem ter suas estruturas tipadas diretamente na declaração ou através de aliases e interfaces.

    Tipagem Anônima / Inline:

    TypeScript

    const usuario: {
    nome: string;
    idade: number;
    } = {
    nome: "Lucas",
    idade: 20
    };
    

    TypeScript

    const produto: {
    nome: string;
    preco: number;
    disponivel: boolean;
    } = {
    nome: "Mouse",
    preco: 100,
    disponivel: true
    };
    

    Para objetos que se repetem ao longo da aplicação, o padrão recomendado é a extração da assinatura para uma interface ou type reutilizável.

    Tipagem de Funções

    A tipagem de funções estabelece os contratos para a entrada (parâmetros) e saída (retorno).

    TypeScript

    function somar(a: number, b: number): number {
    return a + b;
    }
    

    TypeScript

    function criarMensagem(nome: string): string {
    return `Olá, ${nome}`;
    }
    

    Ao definir os tipos dos parâmetros e o retorno, garante-se que a função seja chamada com os dados corretos e que quem consome seu resultado saiba exatamente a estrutura que irá receber.

    TypeScript no Front-end

    No desenvolvimento Front-end moderno, interfaces visuais trabalham continuamente com o fluxo de dados (estados, payloads de requisições, eventos de formulários e propriedades de componentes).

    O TypeScript garante consistência arquitetural ao integrar a camada de serviço (APIs) com a camada de visualização.

    Tipagem de Consumo de APIs

    TypeScript

    interface Usuario {
    id: number;
    nome: string;
    email: string;
    }
    
    const usuarioAPI: Usuario = {
    id: 1,
    nome: "Lucas",
    email: "lucas@email.com"
    };
    

    Funções de Renderização e Formatação

    TypeScript

    interface Produto {
    nome: string;
    preco: number;
    }
    
    function mostrarProduto(produto: Produto): string {
    return `
      Produto: ${produto.nome}
      Preço: R$ ${produto.preco.toFixed(2)}
    `;
    }
    

    O uso de TypeScript em aplicações Front-end evita que propriedades inexistentes tentem ser lidas na interface, eliminando uma das causas mais comuns de erros no navegador (Cannot read property of undefined).

    O Impacto do TypeScript no Desenvolvimento

    É preciso ter clareza sobre os limites da linguagem: o TypeScript não impede todos os erros da aplicação.

    O TypeScript não garante isoladamente que:

    • Uma API remota estará sempre disponível;
    • O servidor responderá sem falhas de rede;
    • A lógica de negócio desenhada pelo desenvolvedor está correta;
    • O usuário final preencherá formulários com dados válidos;
    • Não ocorrerão exceções em tempo de execução (runtime).

    O papel do TypeScript é atuar como uma camada de análise estática durante o desenvolvimento, identificando incompatibilidades de estruturas e assinaturas antes que o código transpilado seja enviado para execução.

    TypeScript
      ↓
    Análise estática no desenvolvimento (IDE / Compilador)
      ↓
    Identificação de incompatibilidades
      ↓
    Transpilação para JavaScript
      ↓
    Execução real no Navegador / Servidor
    

    JavaScript ou TypeScript?

    A escolha não deve ser encarada como uma disputa.

    JavaScript  vs  TypeScript
    

    O TypeScript constrói suas abstrações sobre o ecossistema do JavaScript. Portanto, dominar TypeScript exige uma compreensão sólida do próprio JavaScript.

    Não basta dominar sintaxes exclusivas como:

    • interface
    • type
    • Generics (<T>)

    Se não houver domínio sobre os fundamentos da linguagem base:

    • Funções e contexto de execução;
    • Objetos e mutabilidade;
    • Métodos de manipulação de Arrays;
    • Escopo;
    • Programação assíncrona (Promises, async/await);
    • Sistema de Módulos (import/export).

    O TypeScript enriquece o processo de engenharia de software adicionando previsibilidade, mas o JavaScript permanece como o pilar de execução.

    image

    Comparação Completa: Tetris em JavaScript × Tetris em TypeScript

    Os dois projetos possuem o mesmo objetivo: construir um Tetris funcional no navegador. A diferença fundamental é que o segundo projeto pega a lógica existente e a reorganiza para um ambiente com tipagem estática.

    PRIMEIRO PROJETO (JavaScript):
    HTML + CSS + JavaScript  ──►  Tetris funcional (Execução direta)
    
    SEGUNDO PROJETO (TypeScript):
    HTML + CSS + TypeScript  ──►  Análise Estática  ──►  Compilação (tsc)  ──►  JavaScript (dist/)  ──►  Tetris funcional
    

    O navegador continua executando JavaScript. O TypeScript atua exclusivamente durante o desenvolvimento e precisa ser compilado para JavaScript antes do runtime.

    A lógica central do jogo foi totalmente preservada. Ambas as versões compartilham as mesmas estruturas para:

    • Configurações globais e estado da aplicação
    • Tabuleiro, peças, criação, movimentação e rotação
    • Verificação de colisão, fixação de peças e limpeza de linhas
    • Pontuação, níveis e exibição da Ghost Piece e Próxima Peça
    • Controles via teclado, pausa, Game Over e loop principal via requestAnimationFrame

    O TypeScript não alterou o funcionamento do sistema, mas sim a forma como a estrutura e os dados do código são definidos e verificados.

    Definição Explícita de Tipos e Contratos

    No JavaScript, o estado e as peças são declarados como objetos dinâmicos sem limitação formal de atributos ou tipos de dados:

    JavaScript

    // JavaScript: Estrutura sem contrato formal
    const state = {
    board: [],
    currentPiece: null,
    nextPiece: null,
    score: INITIAL_SCORE,
    lines: INITIAL_LINES,
    level: INITIAL_LEVEL,
    isPlaying: false,
    isPaused: false,
    isGameOver: false,
    dropInterval: BASE_DROP_INTERVAL,
    lastDropTime: 0,
    animationFrame: null
    };
    
    const PIECES = {
    I: { type: "I", matrix: [[1, 1, 1, 1]] }
    // ...
    };
    

    No TypeScript, foram introduzidos contratos formais para proibir mutações inesperadas ou atribuições inválidas durante a compilação:

    TypeScript

    // TypeScript: Contrato explícito do estado do jogo
    interface GameState {
    board: Board;
    currentPiece: Piece | null;
    nextPiece: Piece | null;
    score: number;
    lines: number;
    level: number;
    isPlaying: boolean;
    isPaused: boolean;
    isGameOver: boolean;
    dropInterval: number;
    lastDropTime: number;
    animationFrame: number | null;
    }
    
    const gameState: GameState = { /* ... */ };
    
    // Restrição exata dos literais de peças (Union Type)
    type PieceType = "I" | "O" | "T" | "S" | "Z" | "J" | "L";
    
    // Atribuição de valor inválido gera erro em tempo de compilação
    // const piece: PieceType = "X"; // Erro!
    

    Type Aliases, Interfaces e Mapeamentos (Record)

    O uso de Aliases e Interfaces traz clareza ao modelo de dados, documentando a função de cada estrutura no próprio código:

    TypeScript

    // Aliases para desacoplamento e legibilidade
    type CellValue = PieceType | null;
    type Matrix = number[][];
    type Board = CellValue[][];
    
    // Modelo abstrato da peça (configuração)
    interface PieceDefinition {
    type: PieceType;
    matrix: Matrix;
    }
    
    // Modelo da peça ativa em jogo (com coordenadas)
    interface Piece {
    type: PieceType;
    matrix: Matrix;
    row: number;
    column: number;
    }
    
    // Relação rígida entre as chaves dos tipos e suas definições
    const PIECES: Record<PieceType, PieceDefinition> = {
    // O compilador exige a implementação de todas as chaves de PieceType
    };
    

    Abstração Segura do DOM e Generics

    No JavaScript, a busca por elementos via document.querySelector() pode retornar null, o que abre margem para erros silenciosos em tempo de execução.

    JavaScript

    // JavaScript: Retorno potencialmente nulo não tratado
    const gameBoard = document.querySelector("#game-board");
    

    No TypeScript, a busca é centralizada por uma função genérica helper que executa checagem fail-fast e converte o elemento para sua interface HTML específica:

    TypeScript

    // TypeScript: Validação + Tipagem com Generics
    function getRequiredElement<T extends Element>(selector: string): T {
    const element = document.querySelector<T>(selector);
    if (!element) {
      throw new Error(`Elemento ${selector} não encontrado.`);
    }
    return element;
    }
    
    // Uso prático com inferência do tipo exato
    const gameBoard = getRequiredElement<HTMLDivElement>("#game-board");
    const startButton = getRequiredElement<HTMLButtonElement>("#start-button");
    

    Assinaturas Explícitas, Type Narrowing e Modo Strict

    A ativação do parâmetro "strict": true no tsconfig.json força a aplicação de tratamento explícito para referências nulas e declarações formais de parâmetro/retorno.

    Declaração de Assinaturas e Funções Puras

    TypeScript

    // Declaração de retornos explícitos
    function createRandomPiece(): Piece { /* ... */ }
    function initializeGame(): void { /* ... */ }
    
    // Redefinição funcional da criação do tabuleiro (desacoplada do estado global)
    function createBoard(): Board {
    return Array.from(
      { length: BOARD_HEIGHT },
      (): CellValue[] => Array<CellValue>(BOARD_WIDTH).fill(null)
    );
    }
    
    // Atribuição controlada pelo chamador
    gameState.board = createBoard();
    

    Type Narrowing para Tratamento de null

    A propriedade currentPiece do estado é definida como Piece | null. Para evitar validações manuais dispersas, utiliza-se a técnica de Type Narrowing centralizada:

    TypeScript

    function getCurrentPiece(): Piece {
    if (gameState.currentPiece === null) {
      throw new Error("Nenhuma peça está atualmente em jogo.");
    }
    // Após o if, o TypeScript infere o retorno estritamente como `Piece`
    return gameState.currentPiece;
    }
    
    // Validações explícitas de estado nulo antes de operações críticas
    if (gameState.nextPiece === null) return;
    
    if (gameState.animationFrame !== null) {
    cancelAnimationFrame(gameState.animationFrame);
    }
    

    Conclusão

    A migração de JavaScript para TypeScript no projeto Tetris melhorou a estrutura de desenvolvimento:

    • Maior segurança na fase de compilação: Erros de tipagem, elementos inexistentes no DOM ou acessos a propriedades nulas são capturados pelo compilador antes da execução.
    • Código auto-documentado: type e interface explicam a arquitetura sem necessidade de comentários adicionais.
    • Sem alteração da lógica original: O TypeScript não altera as regras do jogo, a manipulação de matrizes, a renderização do Canvas ou o ciclo via requestAnimationFrame.
    • Compilação limpa: Tipos e interfaces são removidos no processo de build (type stripping). O resultado executado pelo navegador na pasta dist/ continua sendo JavaScript puro.

    Projeto Tetris JS

    image

    Projeto Tetris ts

    image

    Capítulo 4- Introdução ao React

    Depois de passar por APIs, JavaScript moderno e TypeScript, chegamos a uma das tecnologias mais importantes do desenvolvimento Front-end moderno: React.

    Este capítulo aborda os fundamentos necessários para compreender como uma aplicação React é construída e organizada. O objetivo não é apenas apresentar conceitos isolados, mas entender a relação entre eles. React trabalha com componentes. Componentes recebem informações, podem possuir estado, respondem a eventos e determinam o que deve ser renderizado na interface. A partir dessa estrutura, é possível construir aplicações maiores dividindo responsabilidades em partes menores e reutilizáveis.

    Os assuntos deste capítulo estão diretamente conectados:

    React │ ├── Componentes │ ├── JSX │ ├── Props │ ├── State │ └── Eventos │ ├── Renderização │ ├── Condicional │ ├── Listas │ └── Keys │ ├── Organização │ ├── Componentização │ └── Reutilização │ ├── Hooks │ ├── useState │ └── useEffect │ ├── Compartilhamento de dados │ └── Context │ └── Interação └── Forms
    

    O que é React?

    React é uma biblioteca JavaScript utilizada para construir interfaces de usuário.

    Seu foco principal está na criação e atualização da interface. Em vez de construir toda a página como uma única estrutura extensa, o React permite dividir a interface em partes menores chamadas componentes.

    Uma aplicação pode ser organizada assim:

    Aplicação │ ├── Header │ ├── Sidebar │ ├── Main │ ├── Card │ ├── Card │ └── Card │ └── Footer
    

    Cada uma dessas partes pode ser representada por um componente.

    Um exemplo simples:

    function App() {
      return (
          <main>
              <h1>Olá, React</h1>
    
              <p>Construindo interfaces com componentes.</p>
          </main>
      );
    }
    

    Esse componente descreve uma parte da interface.

    O React trabalha principalmente com a ideia de que a interface deve refletir os dados atuais da aplicação.

    Podemos representar isso de forma simples:

    Dados
     ↓
    Componentes
     ↓
    Interface
    

    Quando determinados dados mudam, o React pode atualizar a interface para refletir essa mudança.

    Esse conceito está diretamente relacionado ao State, que veremos mais adiante.

    React não é uma linguagem de programação

    React utiliza JavaScript.

    Isso significa que conhecimentos de JavaScript continuam sendo fundamentais.

    Uma aplicação React ainda trabalha com:

    • variáveis;
    • funções;
    • objetos;
    • arrays;
    • módulos;
    • eventos;
    • operações assíncronas;
    • Promises;
    • async e await;
    • APIs.

    Por exemplo:

    const user = {
      name: "Lucas",
      role: "Desenvolvedor"
    };
    
    console.log(user.name);
    

    Isso continua sendo JavaScript.

    O React adiciona uma forma específica de organizar e construir interfaces utilizando esses recursos.

    Por isso, aprender React sem possuir uma base mínima de JavaScript pode dificultar bastante o processo.

    Componentes

    Componentes são uma das bases do React.

    Um componente representa uma parte reutilizável da interface.

    Um exemplo:

    function Header() {
      return (
          <header>
              <h1>Meu site</h1>
          </header>
      );
    }
    

    Esse componente pode ser utilizado dentro de outro componente:

    function App() {
      return (
          <>
              <Header />
    
              <main>
                  <p>Conteúdo principal.</p>
              </main>
          </>
      );
    }
    

    A estrutura pode ser interpretada assim:

    App
    │
    ├── Header
    │
    └── Main
    

    Um componente normalmente é uma função JavaScript que retorna uma descrição da interface.

    Componentes devem começar com letra maiúscula

    No React, existe uma convenção importante.

    Componentes devem ser escritos com letra inicial maiúscula:

    function Header() {
      return <header>Header</header>;
    }
    

    E utilizados assim:

    <Header />
    

    Isso ajuda o React a diferenciar componentes de elementos HTML.

    Por exemplo:

    <header>
    

    representa um elemento HTML.

    Enquanto:

    <Header />
    

    representa um componente React.

    JSX

    JSX é uma sintaxe utilizada frequentemente em aplicações React.

    Ela permite escrever estruturas semelhantes ao HTML dentro do JavaScript.

    Exemplo:

    const element = (
      <h1>
          Olá, mundo!
      </h1>
    );
    

    À primeira vista, isso parece HTML.

    Mas JSX não é simplesmente HTML escrito dentro de um arquivo JavaScript.

    Ele permite combinar JavaScript e a descrição da interface.

    Por exemplo:

    function User() {
      const name = "Lucas";
    
      return (
          <h1>
              Olá, {name}
          </h1>
      );
    }
    

    As chaves permitem inserir expressões JavaScript:

    {name}
    

    O resultado será:

    Olá, Lucas
    

    Também é possível realizar operações:

    function Result() {
      const value = 10;
    
      return (
          <p>
              Resultado: {value * 2}
          </p>
      );
    }
    

    JSX e atributos

    Alguns atributos possuem diferenças em relação ao HTML tradicional.

    Por exemplo:

    <label htmlFor="email">
      E-mail
    </label>
    

    E:

    <div className="container">
      Conteúdo
    </div>
    

    Em JSX:

    class
    

    é normalmente escrito como:

    className
    

    Um componente precisa organizar corretamente sua estrutura

    Um componente pode retornar uma estrutura contendo vários elementos.

    Por exemplo:

    function App() {
      return (
          <main>
              <h1>Título</h1>
    
              <p>Descrição</p>
          </main>
      );
    }
    

    Também existe o Fragment:

    function App() {
      return (
          <>
              <h1>Título</h1>
    
              <p>Descrição</p>
          </>
      );
    }
    

    O Fragment permite agrupar elementos sem criar necessariamente um elemento adicional na estrutura final.

    Props

    Props são informações passadas de um componente para outro.

    Imagine um componente:

    function User() {
      return (
          <h2>Lucas</h2>
      );
    }
    

    Esse componente sempre apresenta o mesmo nome.

    Podemos torná-lo mais flexível utilizando Props.

    function User(props) {
      return (
          <h2>{props.name}</h2>
      );
    }
    

    Agora:

    <User name="Lucas" />
    

    E:

    <User name="Maria" />
    

    produzem resultados diferentes utilizando o mesmo componente.

    Uma forma comum de trabalhar com Props é através da desestruturação:

    function User({ name }) {
      return (
          <h2>{name}</h2>
      );
    }
    

    Agora:

    <User name="Lucas" />
    

    O fluxo pode ser representado assim:

    Componente pai
        │
        │ Props
        ▼
    Componente filho
    

    Props permitem reutilização

    Considere um componente de produto:

    function Product({ name, price }) {
      return (
          <article>
              <h2>{name}</h2>
    
              <p>
                  R$ {price}
              </p>
          </article>
      );
    }
    

    Agora podemos reutilizá-lo:

    <Product
      name="Teclado"
      price={150}
    />
    
    <Product
      name="Mouse"
      price={80}
    />
    

    A estrutura do componente permanece.

    Os dados mudam.

    Essa é uma das bases da reutilização no React.

    Props não devem ser modificadas pelo componente filho

    Props representam dados recebidos pelo componente.

    O componente pode utilizar esses dados, mas não deve tratá-los como uma estrutura que pode simplesmente ser modificada diretamente.

    O fluxo principal é:

    Pai
     ↓
    Props
     ↓
    Filho
    

    Esse fluxo ajuda a tornar a movimentação dos dados mais previsível.

    State

    State representa dados que pertencem ao componente e podem mudar durante a execução da aplicação.

    Por exemplo, imagine um contador.

    O valor inicial é:

    0
    

    Depois de uma interação:

    1
    

    Depois:

    2
    

    Esse valor muda.

    Para esse tipo de informação, utilizamos State.

    No React moderno, um dos recursos mais utilizados para isso é o Hook:

    useState
    

    Exemplo:

    import { useState } from "react";
    
    function Counter() {
      const [count, setCount] =
          useState(0);
    
      return (
          <section>
              <p>
                  {count}
              </p>
    
              <button
                  onClick={() => {
                      setCount(count + 1);
                  }}
              >
                  Aumentar
              </button>
          </section>
      );
    }
    

    Aqui temos:

    count
    

    Representa o valor atual.

    E:

    setCount
    

    É utilizado para solicitar a atualização desse valor.

    O fluxo é:

    Interação
      ↓
    Atualização do State
      ↓
    Nova renderização
      ↓
    Interface atualizada
    

    Essa relação entre dados e interface é fundamental no React.

    Eventos

    Uma interface precisa responder às ações do usuário.

    Por exemplo:

    • clicar em um botão;
    • escrever em um campo;
    • enviar um formulário;
    • mover o mouse.

    No React, eventos são associados aos elementos utilizando propriedades.

    Um exemplo:

    function App() {
      function handleClick() {
          console.log("Botão clicado");
      }
    
      return (
          <button
              onClick={handleClick}
          >
              Clique aqui
          </button>
      );
    }
    

    Quando o usuário clica:

    onClick
      ↓
    handleClick()
    

    Também é possível trabalhar com funções diretamente:

    <button
      onClick={() => {
          console.log("Clique");
      }}
    >
      Clique
    </button>
    

    Eventos e State

    Eventos frequentemente alteram o estado da aplicação.

    Por exemplo:

    function Counter() {
      const [count, setCount] =
          useState(0);
    
      function increase() {
          setCount(count + 1);
      }
    
      return (
          <>
              <p>{count}</p>
    
              <button
                  onClick={increase}
              >
                  +
              </button>
          </>
      );
    }
    

    O botão não altera diretamente o HTML.

    Ele altera o State.

    Depois:

    State muda
       ↓
    React renderiza novamente
       ↓
    Interface reflete o novo valor
    

    Renderização

    Renderização é o processo relacionado à criação e atualização da interface a partir dos componentes.

    Um componente pode ser entendido como uma função relacionada à interface.

    Por exemplo:

    function Welcome() {
      return (
          <h1>
              Bem-vindo
          </h1>
      );
    }
    

    O React utiliza o resultado do componente para representar a interface.

    Quando o State muda:

    setCount(10);
    

    o React pode executar novamente o componente para obter uma nova representação baseada no estado atual.

    Exemplo:

    function Counter() {
      const [count, setCount] =
          useState(0);
    
      return (
          <p>
              {count}
          </p>
      );
    }
    

    Se:

    count = 0
    

    a interface apresenta:

    0
    

    Depois:

    count = 5
    

    a interface passa a refletir:

    5
    

    A ideia central é:

    Estado atual
        ↓
    Componente
        ↓
    Interface atual
    

    Renderização condicional

    Nem todos os elementos devem aparecer o tempo inteiro.

    Imagine um usuário autenticado.

    Podemos verificar uma condição:

    function App() {
      const isLoggedIn = true;
    
      return (
          <main>
              {
                  isLoggedIn
                      ? <h1>Bem-vindo</h1>
                      : <h1>Faça login</h1>
              }
          </main>
      );
    }
    

    Se:

    isLoggedIn = true
    

    o primeiro conteúdo será exibido.

    Caso contrário:

    Faça login
    

    Operador lógico

    Também podemos utilizar:

    function App() {
      const hasNotification = true;
    
      return (
          <main>
              <h1>Aplicação</h1>
    
              {
                  hasNotification &&
                  <p>
                      Você possui notificações.
                  </p>
              }
          </main>
      );
    }
    

    A mensagem será renderizada apenas se a condição for verdadeira.

    Listas

    Interfaces frequentemente trabalham com coleções de dados.

    Por exemplo:

    const users = [
      "Lucas",
      "Maria",
      "João"
    ];
    

    Podemos utilizar:

    map()
    

    para transformar esses dados em elementos.

    function UserList() {
      const users = [
          "Lucas",
          "Maria",
          "João"
      ];
    
      return (
          <ul>
              {
                  users.map((user) => (
                      <li>
                          {user}
                      </li>
                  ))
              }
          </ul>
      );
    }
    

    O processo pode ser representado assim:

    Array
    ↓
    map()
    ↓
    Elementos React
    ↓
    Interface
    

    Esse padrão é muito utilizado.

    Keys

    Ao renderizar listas, cada elemento precisa de uma identificação chamada key.

    Exemplo:

    const users = [
      {
          id: 1,
          name: "Lucas"
      },
      {
          id: 2,
          name: "Maria"
      }
    ];
    

    Renderização:

    <ul>
      {
          users.map((user) => (
              <li key={user.id}>
                  {user.name}
              </li>
          ))
      }
    </ul>
    

    A key ajuda o React a identificar os elementos da lista.

    Uma estrutura pode mudar:

    Antes
    
    A
    B
    C
    

    Depois:

    A
    C
    D
    

    As Keys ajudam o React a compreender a identidade dos elementos durante essas alterações.

    Por isso, valores estáveis, como identificadores únicos, são geralmente mais adequados.

    Por que não utilizar sempre o índice?

    É possível encontrar estruturas como:

    users.map((user, index) => (
      <li key={index}>
          {user.name}
      </li>
    ));
    

    Isso pode funcionar em determinadas situações.

    Mas, quando uma lista possui:

    • inserções;
    • remoções;
    • reordenação;
    • elementos com estado;

    o índice pode não representar corretamente a identidade do item.

    Por isso, quando existe um identificador real:

    key={user.id}
    

    geralmente é uma escolha mais adequada.

    Componentização

    Componentização é o processo de dividir uma interface em componentes menores.

    Imagine uma aplicação:

    App
    │
    ├── Header
    ├── Navigation
    ├── ProductList
    │   ├── Product
    │   ├── Product
    │   └── Product
    │
    └── Footer
    

    Em vez de colocar toda a interface dentro de um único componente:

    function App() {
      // Interface inteira
    }
    

    podemos dividir responsabilidades.

    Por exemplo:

    function Header() {
      return (
          <header>
              <h1>Loja</h1>
          </header>
      );
    }
    

    Depois:

    function Footer() {
      return (
          <footer>
              <p>
                  Todos os direitos reservados.
              </p>
          </footer>
      );
    }
    

    E:

    function App() {
      return (
          <>
              <Header />
    
              <main>
                  Conteúdo
              </main>
    
              <Footer />
          </>
      );
    }
    

    A componentização ajuda a organizar sistemas maiores.

    Componentizar não significa dividir tudo

    Existe um erro comum: criar componentes extremamente pequenos sem necessidade.

    Por exemplo:

    App
    └── Main
        └── Section
            └── Title
                 └── Text
    

    Nem toda parte da interface precisa necessariamente se tornar um componente independente.

    A componentização deve possuir um motivo.

    Por exemplo:

    • responsabilidade própria;
    • reutilização;
    • complexidade isolada;
    • organização da interface;
    • lógica específica.

    O objetivo não é criar o maior número possível de arquivos.

    O objetivo é construir uma arquitetura compreensível.

    Reutilização

    Um dos benefícios dos componentes é a reutilização.

    Considere:

    function Button({ text }) {
      return (
          <button>
              {text}
          </button>
      );
    }
    

    Agora:

    <Button text="Salvar" />
    
    <Button text="Cancelar" />
    
    <Button text="Excluir" />
    

    A estrutura é reutilizada.

    Os dados mudam.

    Outro exemplo:

    function ProductCard({
      name,
      price
    }) {
      return (
          <article>
              <h2>{name}</h2>
    
              <p>
                  R$ {price}
              </p>
          </article>
      );
    }
    

    Utilização:

    <ProductCard
      name="Teclado"
      price={150}
    />
    
    <ProductCard
      name="Mouse"
      price={80}
    />
    

    Reutilização reduz a necessidade de duplicar a mesma estrutura diversas vezes.

    Hooks

    Hooks são funções do React que permitem utilizar determinados recursos dentro dos componentes.

    Alguns Hooks conhecidos são:

    useState
    
    useEffect
    

    Eles possuem finalidades diferentes.

    useState

    useState permite adicionar State a componentes funcionais.

    Exemplo:

    import { useState } from "react";
    
    function Counter() {
      const [count, setCount] =
          useState(0);
    
      return (
          <>
              <p>
                  {count}
              </p>
    
              <button
                  onClick={() => {
                      setCount(count + 1);
                  }}
              >
                  Aumentar
              </button>
          </>
      );
    }
    

    A estrutura:

    const [count, setCount] =
      useState(0);
    

    pode ser interpretada assim:

    count
    │
    └── Valor atual
    
    
    setCount
    │
    └── Função para atualizar
    
    
    0
    │
    └── Valor inicial
    

    Atualizações baseadas no valor anterior

    Quando uma atualização depende do valor anterior, podemos utilizar uma função:

    setCount((previousCount) => {
      return previousCount + 1;
    });
    

    Ou:

    setCount(
      previousCount =>
          previousCount + 1
    );
    

    Isso deixa explícito que o novo valor depende do estado anterior.

    useEffect

    useEffect é utilizado para trabalhar com determinados efeitos relacionados ao ciclo de vida da interface.

    Um exemplo simples:

    import { useEffect } from "react";
    
    function App() {
      useEffect(() => {
          console.log(
              "Componente renderizado"
          );
      });
    
      return (
          <h1>
              React
          </h1>
      );
    }
    

    Outro exemplo:

    useEffect(() => {
      console.log("Executado");
    }, []);
    

    O array:

    []
    

    representa as dependências utilizadas pelo efeito.

    Um exemplo com uma variável:

    useEffect(() => {
      console.log(count);
    }, [count]);
    

    Nesse caso, o efeito está relacionado a mudanças em:

    count
    

    useEffect e APIs

    Um dos usos comuns do useEffect é trabalhar com operações assíncronas relacionadas a dados externos.

    Por exemplo:

    import {
      useEffect,
      useState
    } from "react";
    
    function Users() {
      const [users, setUsers] =
          useState([]);
    
      useEffect(() => {
          async function loadUsers() {
              const response =
                  await fetch(
                      "/api/users"
                  );
    
              const data =
                  await response.json();
    
              setUsers(data);
          }
    
          loadUsers();
    
      }, []);
    
      return (
          <div>
              {
                  users.map((user) => (
                      <p key={user.id}>
                          {user.name}
                      </p>
                  ))
              }
          </div>
      );
    }
    

    O fluxo é:

    Componente
      ↓
    useEffect
      ↓
    Requisição
      ↓
    Resposta
      ↓
    Atualização do State
      ↓
    Nova renderização
    

    Isso conecta diretamente conhecimentos anteriores sobre:

    • APIs;
    • Fetch;
    • Request;
    • Response;
    • JSON;
    • Promises;
    • Async/Await.

    Context

    Em aplicações maiores, determinados dados podem precisar ser utilizados por diversos componentes.

    Imagine:

    App
     │
     └── Dashboard
         │
         └── Sidebar
               │
               └── UserProfile
    

    Se apenas:

    UserProfile
    

    precisa de um dado que está no:

    App
    

    uma possibilidade seria passar Props através de vários níveis.

    App
     │ Props
     ▼
    Dashboard
     │ Props
     ▼
    Sidebar
     │ Props
     ▼
    UserProfile
    

    Em algumas situações, isso pode se tornar excessivo.

    O Context oferece uma forma de compartilhar determinados valores entre componentes.

    Criando um Context

    Exemplo:

    import {
      createContext
    } from "react";
    
    const UserContext =
      createContext(null);
    

    Depois, um Provider pode disponibilizar um valor:

    function App() {
      const user = {
          name: "Lucas"
      };
    
      return (
          <UserContext.Provider
              value={user}
          >
              <Dashboard />
          </UserContext.Provider>
      );
    }
    

    Um componente dentro dessa árvore pode acessar esse contexto.

    O Context é útil para determinados dados compartilhados.

    Exemplos comuns:

    • tema;
    • usuário autenticado;
    • idioma;
    • configurações globais.

    Context não substitui toda a organização da aplicação

    Context é útil, mas não significa que todos os dados devem se tornar globais.

    Transformar qualquer informação em Context pode aumentar a complexidade da aplicação.

    É necessário avaliar:

    Quem precisa desse dado?
    

    Se apenas um componente precisa dele, talvez o State local seja suficiente.

    Se vários componentes distribuídos pela aplicação precisam acessar o mesmo valor, o Context pode fazer mais sentido.

    Forms

    Formulários são uma parte importante das aplicações Web.

    No React, campos de formulário geralmente trabalham em conjunto com State.

    Exemplo:

    import { useState } from "react";
    
    function Form() {
      const [name, setName] =
          useState("");
    
      function handleChange(event) {
          setName(
              event.target.value
          );
      }
    
      return (
          <input
              type="text"
              value={name}
              onChange={handleChange}
          />
      );
    }
    

    Aqui existe uma relação entre:

    State
    ↓
    value
    ↓
    Input
    

    E:

    Usuário digita
         ↓
    onChange
         ↓
    Atualização do State
         ↓
    Nova renderização
    

    Enviando um formulário

    Exemplo:

    function Form() {
      const [email, setEmail] =
          useState("");
    
      function handleSubmit(event) {
          event.preventDefault();
    
          console.log(email);
      }
    
      return (
          <form
              onSubmit={handleSubmit}
          >
              <input
                  type="email"
                  value={email}
                  onChange={(event) => {
                      setEmail(
                          event.target.value
                      );
                  }}
              />
    
              <button type="submit">
                  Enviar
              </button>
          </form>
      );
    }
    

    A função:

    event.preventDefault()
    

    impede o comportamento padrão de recarregamento da página durante o envio.

    Depois disso, os dados podem ser:

    • validados;
    • armazenados;
    • enviados para uma API;
    • utilizados para atualizar a interface.

    React como uma estrutura conectada

    Depois de analisar todos esses conceitos, fica mais fácil perceber que eles não funcionam de maneira isolada.

    Uma aplicação pode seguir um fluxo como:

    Componentes
        │
        ▼
    Recebem Props
        │
        ▼
    Possuem State
        │
        ▼
    Respondem a Eventos
        │
        ▼
    Atualizam o State
        │
        ▼
    React renderiza novamente
        │
        ▼
    Interface é atualizada
    

    Em aplicações maiores:

    Componentes
      │
      ├── Props
      │
      ├── State
      │
      ├── Hooks
      │   ├── useState
      │   └── useEffect
      │
      ├── Context
      │
      └── Eventos
    

    Também podemos conectar React com os conhecimentos estudados anteriormente:

    JavaScript
      ↓
    React
      ↓
    Componentes
      ↓
    State
      ↓
    Interface
      ↓
    Eventos
      ↓
    Atualização
    

    E:

    React
      ↓
    useEffect
      ↓
    Fetch
      ↓
    API
      ↓
    Response
      ↓
    JSON
      ↓
    State
      ↓
    Renderização
    

    Conclusão

    React não elimina a necessidade de compreender JavaScript. Pelo contrário: ele utiliza diretamente diversos conceitos da linguagem.

    Arrays são utilizados para renderizar listas.

    Objetos podem representar dados.

    Funções representam componentes e manipuladores de eventos.

    Operações assíncronas permitem buscar dados externos.

    State representa informações que podem mudar.

    A grande mudança está na forma como a interface é organizada.

    Em vez de pensar apenas em uma página completa, o desenvolvimento passa a trabalhar com partes menores:

    Componentes
    

    Esses componentes podem:

    • receber dados através de Props;
    • possuir State;
    • responder a eventos;
    • renderizar conteúdos diferentes;
    • trabalhar com listas;
    • reutilizar estruturas;
    • utilizar Hooks;
    • buscar dados de APIs;
    • compartilhar determinados valores através de Context;
    • controlar formulários.

    O React, portanto, não deve ser entendido apenas como uma coleção de comandos ou funções.

    Ele representa uma forma de estruturar interfaces a partir de componentes e dados.

    A interface deixa de ser apenas uma estrutura estática e passa a refletir o estado atual da aplicação.

    Dados mudam
       ↓
    O componente é atualizado
       ↓
    A interface reflete o novo estado
    

    Essa é uma das ideias centrais do React e uma das bases para compreender o desenvolvimento Front-end moderno.

    image

    const finalizacao = `
    Encerramos mais uma etapa.
    
    
    Não se trata apenas de aprender tecnologias,
    mas de compreender como elas funcionam,
    como se conectam
    e por que são utilizadas.
    
    
    Não estudar apenas para saber usar.
    Estudar para entender como funciona.
    `;
    

    image

    Share
    Recommended for you
    CI&T - Java AI Copilot
    Itaú - Java com Inteligência Artificial
    Nublify - Primeiros passos em IA e Cloud
    Comments (0)
    Recommended for youIBM Bob: IA de Nível Empresarial para Desenvolvedores e Tech Leaders