TypeScript com JavaScript Vanilla

TypeScript com JavaScript Vanilla

Boas vindas a esta aventura pelo maravilhoso mundo do TypeScript! Estou super animado por você aqui comigo, porque hoje vamos construir juntos um projeto e, durante essa jornada, desvendar os conceitos mais importantes que o TypeScript tem a oferecer.

Eu sei, aprender uma nova tecnologia pode parecer um pouco intimidante no início, mas não se preocupe! Estou aqui para te guiar passo a passo, esclarecer suas dúvidas e garantir que essa experiência seja divertida e enriquecedora. E, claro, estamos aqui para aprender juntos, então qualquer tropeço é apenas uma oportunidade de crescimento!

Vamos criar uma pequena aplicação de Lista de Compras utilizando TypeScript e, enquanto desenvolvemos, vou mostrar como essa linguagem incrível pode te ajudar a maximizar sua produtividade e reduzir erros inesperados. E adivinhe só? Vamos fazer isso tudo em um ambiente Vanilla! Sim, você leu certo, sem frameworks complicados, apenas o bom e velho JavaScript, com um toque especial do TypeScript!

Então, preparado para embarcar nessa jornada comigo? Vamos aprender, codificar, enfrentar alguns desafios, e o mais importante, vamos nos divertir no processo! Pegue sua xícara de café (ou chá, se preferir), ajuste sua IDE e vamos começar essa incrível viagem pelo TypeScript!

Mãos à obra: configurando e criando uma lista de compras

Que sensação incrível começar algo novo, não é? Sinto o mesmo frio na barriga que você! Então, relaxe, respire fundo e vamos começar por configurar nosso ambiente de desenvolvimento.

Configurando um projeto Vanilla com TypeScript

Primeiro, vamos esclarecer uma dúvida que pode estar passando pela sua cabeça: “O que significa Vanilla nesse contexto?”. Quando falamos de Vanilla em desenvolvimento web, estamos nos referindo a JavaScript puro, sem frameworks ou bibliotecas externas. É o básico do básico, o ingrediente em sua forma mais simples e pura, por isso o nome “Vanilla”.

Para começar, você vai precisar do Node.js e do NPM (Node Package Manager) instalados na sua máquina. Eles serão nossos fiéis escudeiros, ajudando a gerenciar pacotes e a executar nosso projeto. Caso não tenha eles instalados, dê uma olhada neste artigo, ele vai te guiar direitinho.

Com o Node.js e NPM prontos, abra seu terminal e vamos criar um novo diretório para nosso projeto. Sinta-se em casa, escolha um lugar que goste no seu computador e inicialize um novo projeto Node.js com o comando npm init -y.

Está indo super bem! Agora, o próximo passo é instalar o TypeScript. Vamos usá-lo bastante, então digite npm install -g typescript para instalá-lo globalmente na sua máquina.

Já estamos quase lá! Dentro do diretório raiz do projeto, crie um arquivo chamado tsconfig.json. Ele é a fonte de verdade para a configuração de como queremos que o TypeScript se comporte em relação a nosso projeto. Ele nos permite ligar e desligar funcionalidades. Vamos começar com algumas poucas configurações:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "rootDir": "./",
    "outDir": "./"
  }
}

Vamos dar uma olhada no que cada configuração faz?

  • "target": "es5": Define a versão do ECMAScript que o código compilado vai seguir. Escolhemos "es5" por ser amplamente suportado.

  • "module": "commonjs": Especifica o sistema de módulos que será usado. "commonjs" é uma escolha segura para projetos Node.js.

  • "rootDir": "./": Indica o diretório onde estão nossos arquivos TypeScript.

  • "outDir": "./": Especifica o diretório onde o TypeScript vai colocar os arquivos JavaScript compilados.

Se você ainda não conhece todo esse lance de NPM e pacotes e quer mergulhar mais fundo nesses conceitos para conhecer a teoria por detrás da prática, se liga aqui nesse artigo. Repara que, pra seguirmos aqui codando, não é estritamente necessário ter essa bagagem teórica. Sinta-se livre para escolher o que melhor se encaixa com o seu momento!

Construindo a lista de compras

Vamos iniciar criando nosso arquivo index.html. Esse arquivo será a porta de entrada da nossa aplicação. Para dar um toque especial na interface, vamos utilizar o Bootstrap. Caso você não esteja familiarizado com ele, é um framework de CSS que nos auxilia a estilizar nossa aplicação de forma mais eficiente. Se quiser se aprofundar mais sobre o Bootstrap, sugiro este artigo. Aliás, é provável que eu sempre indique alguns conteúdos adicionais para os temas que não vamos explorar a fundo aqui, ok?

Agora, adicione o CSS do Bootstrap em nosso HTML assim:

<!DOCTYPE html>
<html lang="pt-br">
<head>
  <meta charset="UTF-8">
  <title>Lista de Compras com TypeScript</title>
  <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
    <div class="container mt-5">
        <h1>Lista de Compras com TypeScript</h1>
        <form id="formularioItem">
            <div class="form-group">
                <label for="item">Item:</label>
                <input type="text" class="form-control" id="item" required>
            </div>
            <button type="submit" class="btn btn-primary">Adicionar</button>
        </form>
        <ul id="listaItens" class="list-group mt-3">
            <!-- Os itens da lista serão inseridos aqui -->
        </ul>
    </div>
    <script src="app.js"></script>
</body>
</html>

Agora, você deve estar se perguntando: "Por que não começamos a escrever o CRUD diretamente dentro de uma tag script?" Bem, aqui entra uma peculiaridade do TypeScript! Vamos criar um arquivo .ts separado, pois, diferente do JavaScript, o TypeScript precisa ser "compilado".

Vamos criar agora um arquivo chamado app.ts. Podemos finalmente começar a escrever nossa aplicação. Vamos começar com a base da nossa aplicação. Precisamos definir como um item da lista de compra é. Quais propriedades ele deveria ter e qual é o tipo de cada uma dessas propriedades. No TypeScript, uma interface é uma forma poderosa de definir contratos dentro do seu código e contratos com o código fora do seu projeto. No nosso caso, a interface Item foi definida assim:

interface Item {
  id: string;
  nome: string;
}

Esta interface está descrevendo a forma que um objeto Item deve ter. Nesse caso, todo item deve ter um id e um nome, ambos do tipo string. Ao usar esta interface, estamos basicamente dizendo ao TypeScript: "Ei, queremos objetos que tenham essa forma específica!". Isso nos proporciona mais segurança, pois o TypeScript irá gerar um erro se tentarmos criar um item que não adira a essa forma.

Já para interagir com o DOM, precisamos selecionar os elementos com os quais desejamos trabalhar. No entanto, o TypeScript não conhece o tipo de elemento que document.getElementById irá retornar, por isso precisamos usar o type casting para informar ao TypeScript o tipo de elemento com que estamos trabalhando:

// Selecionando elementos do DOM
const formularioItem = document.getElementById('formularioItem') as HTMLFormElement;
const listaItens = document.getElementById('listaItens') as HTMLUListElement;
const inputItem = document.getElementById('item') as HTMLInputElement;

Nesses casos, estamos usando o type assertion para informar ao TypeScript que formularioItem é um elemento de formulário, listaItens é uma lista não ordenada e inputItem é um elemento de input. Isso é importante porque agora o TypeScript sabe quais propriedades e métodos estão disponíveis para esses elementos, melhorando a auto completação e detectando erros.

Você deve estar se perguntando: de onde vêm esses tipos que usamos para o type assertion? Bem, eles são parte do DOM (Document Object Model) Type Definition fornecido pelo TypeScript. Quando você trabalha com TypeScript no ambiente de navegador, o compilador utiliza um conjunto de definições de tipos chamado lib.dom.d.ts para entender os tipos associados ao DOM. Esses tipos representam os elementos HTML e suas propriedades, métodos e eventos, e são derivados das especificações da web. Portanto, HTMLFormElement, HTMLUListElement e HTMLInputElement são tipos que representam, respectivamente, um elemento de formulário HTML, uma lista não ordenada e um elemento de input. Utilizá-los proporciona autocompletar código inteligente e verificações de tipo, facilitando a manipulação segura dos elementos do DOM.

Vamos agora criar uma função que carrega nossos itens:

// Carregando itens do localStorage
const carregarItens = (): Item[] => {
    const itens = localStorage.getItem('itens');
    return itens ? JSON.parse(itens) : [];
};

Acho que mesmo sendo uma função simples de 4 linhas, ela ensina bastante coisa pra gente:

  • Tipagem de retorno Item[]: A função tem um tipo de retorno explícito Item[], que indica que essa função retornará um array de objetos do tipo Item. Explicitamente tipar a função é uma prática recomendada em TypeScript para garantir a consistência dos tipos de dados manipulados na aplicação. Ou seja, quem chamar essa função já sabe o que esperar dela.
  • A linha const itens = localStorage.getItem('itens'); busca no localStorage por uma entrada com a chave 'itens'. Se encontrada, a entrada é atribuída à constante itens como uma string JSON.
  • Condicional ternário e JSON.parse: A linha return itens ? JSON.parse(itens) : []; faz uso de um operador ternário. Se itens for verdadeiro (ou seja, se a entrada 'itens' existir no localStorage), a função retornará o valor de itens parseado de volta para um objeto JavaScript utilizando JSON.parse(itens). Caso contrário, a função retornará um array vazio [].

Essa abordagem proporciona um manejo seguro e eficiente de dados, garantindo que a aplicação possa lidar adequadamente com cenários onde 'itens' pode não estar presente no localStorage.

Vamos então definir a função que salva os itens na localStorage:

// Salvando itens no localStorage
const salvarItens = (itens: Item[]) => {
    localStorage.setItem('itens', JSON.stringify(itens));
};

Nesta função, estamos lidando com o conceito de tipagem implícita. Isso acontece porque, embora estejamos especificando o tipo do parâmetro de entrada itens como um array de Item, não estamos explicitamente anotando o tipo de retorno da função. O TypeScript é inteligente o suficiente para inferir o tipo de retorno baseado no que a função faz. Neste caso, como a função não tem uma instrução de return, o TypeScript infere que o tipo de retorno é void, que significa que a função não retorna nada. A gente pode fazer isso de forma explícita também, assim:

// Salvando itens no localStorage
const salvarItens = (itens: Item[]): void => {
    localStorage.setItem('itens', JSON.stringify(itens));
};

Reparou na diferença? Adicionamos : void ao final da declaração da função. Isso é uma forma de dizer explicitamente ao TypeScript: "Ei, essa função não vai retornar nada!".

O tipo void em TypeScript é utilizado para representar a ausência de um valor. Quando dizemos que uma função é do tipo void, estamos afirmando que essa função não retorna nenhum valor. Simplificando, é como se a função dissesse: "Vou realizar uma tarefa, mas não vou devolver nenhum resultado para você!"

Agora que já temos uma noção de como o TypeScript tipa as coisas (ba dum tsss), vamos escrever o restante da nossa aplicação. Precisamos dos métodos:

  • adicionarItem
  • removerItem
  • editarItem
  • renderizarItens

Além disso, vamos precisar adicionar um listener que escuta o evento de submit do formulário. Que tal você experimentar? Ainda não conseguimos testar, mas você pode depois comparar o seu resultado com o meu e aprender com as diferenças. Se quiser até publicar a sua versão, manda ver e me marca lá no Instagram ou no LinkedIn.

A minha versão completa do app.ts ficou assim:


// Definindo a interface para o tipo Item
interface Item {
    id: string;
    nome: string;
}

// Selecionando elementos do DOM
const formularioItem = document.getElementById('formularioItem') as HTMLFormElement;
const listaItens = document.getElementById('listaItens') as HTMLUListElement;
const inputItem = document.getElementById('item') as HTMLInputElement;

// Carregando itens do localStorage
const carregarItens = (): Item[] => {
    const itens = localStorage.getItem('itens');
    return itens ? JSON.parse(itens) : [];
};

// Salvando itens no localStorage
const salvarItens = (itens: Item[]) => {
    localStorage.setItem('itens', JSON.stringify(itens));
};

// Adicionando um novo item
const adicionarItem = (nome: string) => {
    const itens = carregarItens();
    const novoItem: Item = {
        id: new Date().toISOString(),
        nome
    };
    itens.push(novoItem);
    salvarItens(itens);
};

// Removendo um item pelo ID
const removerItem = (id: string) => {
    const itens = carregarItens();
    const itensAtualizados = itens.filter(item => item.id !== id);
    salvarItens(itensAtualizados);
};

// Editando um item pelo ID
const editarItem = (id: string, novoNome: string) => {
    const itens = carregarItens();
    const item = itens.find(item => item.id === id);
    if (item) {
        item.nome = novoNome;
        salvarItens(itens);
    }
};

// Renderizando a lista de itens
const renderizarItens = () => {
    const itens = carregarItens();
    listaItens.innerHTML = '';
    itens.forEach(item => {
        const listItem = document.createElement('li');
        listItem.className = 'list-group-item';
        listItem.textContent = item.nome;
        listaItens.appendChild(listItem);

        // Adicionando eventos para editar e remover o item
        listItem.addEventListener('dblclick', () => {
            const novoNome = prompt('Editar item:', item.nome);
            if (novoNome !== null) editarItem(item.id, novoNome);
            renderizarItens();
        });

    });
};

// Inicializando a aplicação
formularioItem.addEventListener('submit', (e) => {
    e.preventDefault();
    const nome = inputItem.value.trim();
    if (nome) {
        adicionarItem(nome);
        inputItem.value = '';
        renderizarItens();
    }
});

// Renderizando itens ao carregar a página
renderizarItens();

Perfeito! Já temos nosso app.ts todo prontinho, cheio de funcionalidades e com uma pitada de aprendizado sobre TypeScript. Agora, vamos entrar em uma parte igualmente empolgante: compilar nosso código TypeScript para JavaScript, incluí-lo no nosso index.html e finalmente ver nossa aplicação ganhar vida!

Compilando o TypeScript para JavaScript

Então, vamos lá! Abre o terminal e, no diretório do projeto, digita o seguinte comando:

tsc

Este comando vai instruir o TypeScript a compilar o arquivo app.ts para JavaScript, gerando um arquivo app.js. Lembra do nosso tsconfig.json? Ele já está configurado para ajudar nesse processo, então não precisamos nos preocupar com detalhes adicionais por enquanto.

Lembra que, enquanto preparávamos o nosso ambiente, instalamos o typescript globalmente? É por isso que agora conseguimos rodar o comando tsc, porque o NPM instalou em nosso computador tudo o que o TS precisa. Legal ver as coisas se conectando, não é?

Com o app.js gerado, precisamos incluí-lo no nosso index.html. Para isso, adicione a seguinte tag script ao final do body do seu HTML:

<script src="app.js"></script>

Dessa forma, estamos linkando o arquivo JavaScript gerado ao nosso HTML, e todo o código que escrevemos em TypeScript será executado corretamente no navegador.

Testando a aplicação

Agora é o momento que estávamos esperando! Abre o arquivo index.html em seu navegador preferido e veja sua aplicação em ação. Você deverá ser capaz de adicionar e remover itens da lista de compras, e, graças ao localStorage, esses itens serão mantidos mesmo que você recarregue a página.

Aproveita pra testar a edição, no meu caso eu coloquei um listener para um duplo click no item.

O meu projeto final eu deixei pra você aqui no GitHub.

Banner de divulgação da Imersão IA da Alura em colaboração com o Google. Mergulhe em Inteligência artificial com a Alura e o Google. Serão cinco aulas gratuitas para você aprender a usar IA na prática e desenvolver habilidades essenciais para o mercado de trabalho. Inscreva-se gratuitamente agora!

Próximos passos

Bom, chegamos ao fim dessa jornada inicial pelo TypeScript! É natural que, quando já temos uma certa bagagem com JavaScript, possamos ter um pé atrás em relação ao TypeScript. É como sair da zona de conforto, não é mesmo? Mas acredite, é um passo que vale muito a pena!

Em projetos maiores, e especialmente naqueles que não fomos nós que escrevemos do zero, o TypeScript se torna um aliado inestimável. Ele nos ajuda a entender como as coisas funcionam, previne uma série de erros comuns e facilita a manutenção do código.

Se você gostou dessa introdução e quer continuar desbravando o TypeScript, tenho uma dica de ouro! Confere a formação sobre TypeScript aqui na Alura. Nela você vai aprender ainda mais com o Jhonatan, a Moni e comigo! Vamos explorar juntos muitos outros recursos e desafios que essa linguagem tem a oferecer.

E, claro, não se esqueça de me seguir nas redes sociais! Estou sempre compartilhando dicas, insights e conteúdos valiosos sobre frontend e outras tecnologias. É uma ótima forma de continuarmos essa conversa e aprofundarmos ainda mais os conhecimentos!

Então é isso, espero que você tenha curtido essa aventura tanto quanto eu. Estou animado para ver o que você vai criar com o TypeScript! Lembre-se, o aprendizado é contínuo e cada novo desafio é uma oportunidade de crescimento.

Compartilha a sua lista de compras com a hashtag #aprendinaalura e me marca!

Vida longa e próspera!

Vinicios Neves
Vinicios Neves

Vinicios é engenheiro de software, envolvido na arquitetura, design e implementação de microsserviços, micro frontends e sistemas distribuídos. Tem experiência significativas em aplicativos, integração e arquitetura corporativa. É Engenheiro de Software pela UNESA e Arquiteto de Software pela PUC Minas.

Veja outros artigos sobre Front-end