Sua dose diária de programação

Conceito, 3 exemplos e 3 exercícios: uma pequena conquista de cada vez.

✦   Lição

Variáveis e tipos básicos

Uma variável é um nome para um valor. Em TypeScript você a declara com const, quando o valor não vai mudar, ou com let, quando vai. A regra prática é começar com const e trocar para let só quando precisar reatribuir; var é a forma antiga e não deve ser usada em código novo.

TypeScript Fundamentos Iniciante 3 exemplos + 3 exercícios
42 lições publicadas 10 linguagens novas lições todo dia útil
TypeScript / Fundamentos

Variáveis e tipos básicos

por Ellery Blackwood
Iniciante

Entenda o conceito

Uma variável é um nome para um valor. Em TypeScript você a declara com const, quando o valor não vai mudar, ou com let, quando vai. A regra prática é começar com const e trocar para let só quando precisar reatribuir; var é a forma antiga e não deve ser usada em código novo.

Toda variável tem um tipo, anotado depois do nome (let quantidade: number = 20;) ou inferido pelo valor inicial (let moedas = 150; já é number). O tipo fica fixo: a variável pode receber outro valor, mas do mesmo tipo. Os tipos primitivos do dia a dia são string, number e boolean, e o operador typeof mostra o tipo de um valor enquanto o programa roda.

Dois valores especiais representam ausência: null (vazio de propósito) e undefined (ainda sem valor). No modo strict do tsc, uma variável só aceita null se o tipo disser isso, como em string | null. Para mostrar um valor padrão quando algo está ausente, use o operador ??.

↳ Pontos-chave
  • Use const por padrão e let só para o que será reatribuído; evite var.
  • O tipo vem da anotação (let x: number = 1) ou é inferido do valor inicial, e não muda depois.
  • Texto que veio de fora precisa de Number(...) antes da conta; '150' + '75' concatena e dá '15075'.
  • null e undefined indicam ausência; o tipo precisa permitir, como em string | null.
  • a ?? b devolve b só quando a é null ou undefined; || também troca 0, '' e false.

Do conceito à prática

3 exemplos para ver a ideia funcionando.

01const e let no estoque da loja

estoque.ts
// Estoque de uma loja: const para o que não muda, let para o que muda.

// O nome do produto nunca muda: const.
const produto: string = 'Caneca de cerâmica';
// A quantidade muda a cada venda ou reposição: let.
let quantidade: number = 20;

console.log(`Produto: ${produto}`);
console.log(`Estoque inicial: ${quantidade}`);

// Venda de 3 unidades: -= subtrai e guarda de volta.
quantidade -= 3;
console.log(`Após vender 3: ${quantidade}`);

// Chegou uma caixa com 12 unidades.
quantidade += 12;
console.log(`Após repor 12: ${quantidade}`);

// ++ soma 1: um cliente devolveu uma caneca.
quantidade++;
console.log(`Após 1 devolução: ${quantidade}`);

// produto = 'Prato'; // erro: não é possível atribuir a uma const
// quantidade = 'trinta'; // erro: string não é atribuível a number

O nome do produto nunca muda, por isso é const; a quantidade sobe e desce, por isso é let. Os operadores compostos -= e += fazem a conta e guardam o resultado na mesma variável, e ++ soma 1, o jeito mais curto de registrar uma unidade a mais.

As duas linhas comentadas no fim mostram o que o tsc recusa: reatribuir uma const e guardar texto numa variável number. O let libera a troca de valor, nunca a troca de tipo.

02Inferência, typeof e conversão no inventário do jogo

inventario.ts
// Inventário de um jogo: inferência, typeof e conversão de texto em número.

// Sem anotação, o tipo vem do valor inicial.
let moedas = 150; // number
let heroi = 'Lia'; // string
let temEscudo = false; // boolean

// typeof diz o tipo do valor em tempo de execução.
console.log(`moedas: ${typeof moedas}`);
console.log(`heroi: ${typeof heroi}`);
console.log(`temEscudo: ${typeof temEscudo}`);

// O baú devolve texto; Number() converte para número antes da conta.
const premioDoBau: string = '75';
moedas = moedas + Number(premioDoBau);
console.log(`${heroi} agora tem ${moedas} moedas.`);

// Com + entre texto e número, o JavaScript concatena: cuidado.
console.log(`Sem converter: ${'150' + premioDoBau}`);

// Texto que não é número vira NaN ("not a number").
const lido = Number('dez');
console.log(`Number('dez'): ${lido}, é NaN? ${Number.isNaN(lido)}`);

// Comprou um escudo: o boolean muda de valor, mas continua boolean.
temEscudo = true;
moedas -= 120;
console.log(`Escudo: ${temEscudo}, moedas restantes: ${moedas}`);

As três variáveis não têm anotação, mas o TypeScript infere number, string e boolean pelos valores iniciais, e o typeof confirma isso enquanto o programa roda. O prêmio do baú chega como texto ('75'), situação comum com dados vindos de arquivos ou formulários, e Number() o converte antes da soma.

A linha "Sem converter" mostra o perigo: com +, texto e texto são concatenados, e '150' + '75' vira '15075'. Já Number('dez') resulta em NaN, um valor do tipo number que significa "não é número"; Number.isNaN é o teste confiável para ele.

03null, undefined e ?? no empréstimo da biblioteca

emprestimo.ts
// Empréstimo na biblioteca: null, undefined e o operador ??.

const livro: string = 'O Cortiço';
const leitor: string = 'Carlos';

// string | null: ainda não foi devolvido, então o valor começa como null.
let devolvidoEm: string | null = null;

// Variável declarada sem valor começa como undefined.
let observacao: string | undefined;

console.log(`Livro: ${livro} (leitor: ${leitor})`);
console.log(`devolvidoEm: ${devolvidoEm} (typeof: ${typeof devolvidoEm})`);
console.log(`observacao: ${observacao} (typeof: ${typeof observacao})`);

// ?? usa o valor da direita quando o da esquerda é null ou undefined.
console.log(`Situação: ${devolvidoEm ?? 'emprestado'}`);
console.log(`Nota: ${observacao ?? 'sem observações'}`);

// O livro voltou: agora a variável guarda uma string.
devolvidoEm = '2026-10-16';
observacao = 'capa levemente amassada';

console.log(`Situação: ${devolvidoEm ?? 'emprestado'}`);
console.log(`Nota: ${observacao ?? 'sem observações'}`);

// ?? é diferente de ||: com ||, o 0 também seria trocado.
const diasDeAtraso: number = 0;
console.log(`Atraso com ??: ${diasDeAtraso ?? 'desconhecido'}`);
console.log(`Atraso com ||: ${diasDeAtraso || 'desconhecido'}`);

devolvidoEm começa como null porque o livro ainda não voltou, e o tipo string | null deixa isso explícito. observacao é declarada sem valor e por isso começa como undefined. Note o detalhe histórico do JavaScript: typeof null é 'object', não 'null'.

O operador ?? troca só null e undefined pelo valor padrão. As duas últimas linhas mostram a diferença para ||, que considera 0 um valor "falso" e o substitui por 'desconhecido', apagando uma informação válida (zero dias de atraso).


Agora é com você

Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.

01Placar de vidas e pontosFácil

Escreva um programa que acompanhe as vidas e os pontos de um jogador ao longo de uma partida, imprimindo o placar depois de cada evento. Fase 1: ganha 50 pontos; fase 2: ganha 30 pontos e perde uma vida; bônus: os pontos dobram; no fim, ganha uma vida extra.

↳ Requisitos
  • Declarar o nome do jogador com const e vidas e pontos com let, todos com anotação de tipo.
  • Atualizar os valores com +=, -=, *= e ++, sem escrever os resultados à mão.
  • Imprimir as seis linhas com console.log e template literals, exatamente como na saída esperada.
código inicial
const jogador: string = 'Duda';
// declare vidas (3) e pontos (0) com let

console.log(`Jogador: ${jogador}`);
// imprima o início e atualize o placar a cada evento
saída esperada
Jogador: Duda
Início: 3 vidas, 0 pontos
Fase 1: 3 vidas, 50 pontos
Fase 2: 2 vidas, 80 pontos
Bônus: 2 vidas, 160 pontos
Final: 3 vidas, 160 pontos
Ver dica

Uma variável declarada com const não pode receber outro valor; o que muda durante a partida precisa ser let.

Ver resposta
// Placar de um jogador: const para o nome, let para o que muda.
const jogador: string = 'Duda';
let vidas: number = 3;
let pontos: number = 0;

console.log(`Jogador: ${jogador}`);
console.log(`Início: ${vidas} vidas, ${pontos} pontos`);

// Fase 1: só ganha pontos.
pontos += 50;
console.log(`Fase 1: ${vidas} vidas, ${pontos} pontos`);

// Fase 2: ganha 30 pontos, mas perde uma vida.
pontos += 30;
vidas -= 1;
console.log(`Fase 2: ${vidas} vidas, ${pontos} pontos`);

// Bônus: os pontos dobram.
pontos *= 2;
console.log(`Bônus: ${vidas} vidas, ${pontos} pontos`);

// Vida extra no fim da partida.
vidas++;
console.log(`Final: ${vidas} vidas, ${pontos} pontos`);

Passo a passo

  1. jogador é const porque o nome não muda durante a partida; vidas e pontos são let porque recebem valores novos a cada evento.
  2. As anotações : string e : number deixam explícito o tipo de cada variável. Mesmo com let, o tipo fica fixo: só o valor muda.
  3. pontos += 50 é o mesmo que pontos = pontos + 50, só que mais curto. Na fase 2, += e -= atualizam as duas variáveis.
  4. pontos *= 2 multiplica e guarda de volta, transformando 80 em 160.
  5. vidas++ soma 1 à variável; é a forma usual de contar "mais um".
  6. Cada console.log repete o mesmo molde de texto, então a saída mostra a evolução do placar passo a passo.

Por que funciona

O programa roda de cima para baixo e cada operação parte do valor atual da variável. Por isso os resultados (50, 80, 160) são calculados pelo código, e não digitados: se a pontuação de uma fase mudar, as linhas seguintes se ajustam sozinhas. A escolha entre const e let documenta a intenção: quem lê sabe na hora o que pode mudar.

Erros comuns

  • Declarar const pontos: number = 0; e depois fazer pontos += 50: o tsc acusa TS2588 ("Cannot assign to 'pontos' because it is a constant") e o Node lança TypeError: Assignment to constant variable. Use let.
  • Escrever pontos = 160; no bônus: a saída fica igual, mas o requisito pede o cálculo com *= e o valor deixa de acompanhar as fases anteriores.
  • Atribuir texto a uma variável number, como vidas = 'duas';: erro TS2322 no tsc. O let permite trocar o valor, não o tipo.

Outra forma de resolver

Como os valores iniciais deixam o tipo óbvio, dá para escrever let vidas = 3; e deixar o TypeScript inferir number. Também é possível guardar o molde da linha numa função placar(): string para não repetir o template literal; vale a pena quando o formato da linha começa a crescer.

Saída esperada

Jogador: Duda
Início: 3 vidas, 0 pontos
Fase 1: 3 vidas, 50 pontos
Fase 2: 2 vidas, 80 pontos
Bônus: 2 vidas, 160 pontos
Final: 3 vidas, 160 pontos

02Pedido com campos em textoMédio

Os campos de um pedido da loja chegaram de um formulário como texto: quantidade '3', preço '19.90' e cupom de desconto '10' (por cento). Converta-os em números, calcule subtotal, desconto e total e verifique se o campo 'dois' representa um número.

↳ Requisitos
  • Converter os textos com Number() e guardar os resultados em constantes anotadas como number.
  • Mostrar com typeof o tipo de quantidade e preço antes e depois da conversão.
  • Exibir os valores em reais com toFixed(2), fazendo as contas com os números exatos.
  • Testar o campo 'dois' com Number.isNaN e guardar o resultado numa constante boolean.
código inicial
const quantidadeTexto: string = '3';
const precoTexto: string = '19.90';
const cupomTexto: string = '10';
const campoInvalido: string = 'dois';

// converta os textos com Number() e calcule o pedido
saída esperada
quantidade: '3' (string) -> 3 (number)
preco: '19.90' (string) -> 19.9 (number)
Subtotal: R$ 59.70
Desconto (10%): R$ 5.97
Total: R$ 53.73
Campo 'dois' válido? false
Ver dica

Sem converter, quantidadeTexto * precoTexto é erro no tsc: os operadores aritméticos exigem number.

Ver resposta
// Pedido da loja: os campos chegam do formulário como texto.
const quantidadeTexto: string = '3';
const precoTexto: string = '19.90';
const cupomTexto: string = '10';
const campoInvalido: string = 'dois';

// Number() converte o texto em número antes de qualquer conta.
const quantidade: number = Number(quantidadeTexto);
const preco: number = Number(precoTexto);
const cupom: number = Number(cupomTexto);

console.log(`quantidade: '${quantidadeTexto}' (${typeof quantidadeTexto}) -> ${quantidade} (${typeof quantidade})`);
console.log(`preco: '${precoTexto}' (${typeof precoTexto}) -> ${preco} (${typeof preco})`);

const subtotal: number = quantidade * preco;
const desconto: number = (subtotal * cupom) / 100;
const total: number = subtotal - desconto;

// toFixed(2) só na hora de exibir: as contas usam o valor exato.
console.log(`Subtotal: R$ ${subtotal.toFixed(2)}`);
console.log(`Desconto (${cupom}%): R$ ${desconto.toFixed(2)}`);
console.log(`Total: R$ ${total.toFixed(2)}`);

// Texto que não representa número vira NaN.
const valida: boolean = !Number.isNaN(Number(campoInvalido));
console.log(`Campo '${campoInvalido}' válido? ${valida}`);

Passo a passo

  1. Os quatro campos são string, como chegariam de um formulário ou de um arquivo de texto.
  2. Number(quantidadeTexto) converte '3' em 3, e Number(precoTexto) converte '19.90' em 19.9. As constantes recebem a anotação number, que é o tipo devolvido por Number().
  3. As duas primeiras linhas usam typeof antes e depois da conversão, mostrando que o valor passou de string para number.
  4. O subtotal é 3 * 19.9, o desconto é 10% do subtotal e o total é a diferença. Os parênteses em (subtotal * cupom) / 100 deixam a ordem da conta clara.
  5. toFixed(2) aparece só na exibição. Ele arredonda para duas casas e devolve texto, por isso não é usado nas contas.
  6. Number('dois') dá NaN; Number.isNaN devolve true, e o ! inverte o resultado para responder "é válido?".

Por que funciona

Operações aritméticas só fazem sentido com number, e o tsc impede que um string entre direto numa multiplicação. A conversão explícita torna esse passo visível no código. O resultado exato de 3 * 19.9 é 59.699999999999996, por causa da forma como o computador guarda decimais; arredondar só na exibição evita acumular esses pequenos erros nas contas seguintes.

Erros comuns

  • Somar sem converter: quantidadeTexto + precoTexto não dá erro no tsc (+ também junta textos), mas resulta em '319.90'. Converta antes.
  • Testar NaN com igualdade: Number('dois') === NaN é sempre false, porque NaN não é igual nem a si mesmo. Use Number.isNaN.
  • Anotar const subtotal: number = (quantidade * preco).toFixed(2);: erro TS2322, pois toFixed devolve string.

Outra forma de resolver

parseFloat(precoTexto) também converte texto em número e ignora o que vier depois dos dígitos (parseFloat('19.90 reais') dá 19.9). Ele é útil para textos "sujos", mas pode esconder erros de digitação; para campos que deveriam conter só o número, Number() é mais rigoroso.

Saída esperada

quantidade: '3' (string) -> 3 (number)
preco: '19.90' (string) -> 19.9 (number)
Subtotal: R$ 59.70
Desconto (10%): R$ 5.97
Total: R$ 53.73
Campo 'dois' válido? false

03Multas e avisos da bibliotecaDesafio

Calcule as multas de uma leitora com três empréstimos (Dom Casmurro com 0 dia de atraso, Iracema com 4 e O Guarani com 2), a R$ 1,50 por dia. Ela não informou telefone, e um aviso só deve existir se o total de multas passar de R$ 5,00; valores ausentes aparecem com um texto padrão.

↳ Requisitos
  • Criar function multa(diasAtraso: number): number e acumular o total em let total: number = 0 com +=.
  • Declarar telefone e aviso com o tipo string | null, começando em null; aviso recebe o texto só se o total passar de 5.
  • Exibir telefone e aviso com o operador ?? (padrões 'não informado' e 'nenhum').
  • A saída deve ser exatamente igual à esperada, com os valores em reais formatados por toFixed(2).
código inicial
function multa(diasAtraso: number): number {
  // R$ 1,50 por dia de atraso
  return 0;
}

const leitora: string = 'Marina';
// declare telefone e aviso como string | null e o total com let

console.log(`Leitora: ${leitora}`);
saída esperada
Leitora: Marina
Telefone: não informado
Dom Casmurro: 0 dia(s) de atraso, multa R$ 0.00
Iracema: 4 dia(s) de atraso, multa R$ 6.00
O Guarani: 2 dia(s) de atraso, multa R$ 3.00
Total de multas: R$ 9.00
Aviso: Pagar antes do próximo empréstimo
Ver dica

Para o aviso começar vazio e receber texto depois, ele precisa ser let e o tipo precisa aceitar os dois casos.

Ver resposta
// Multas da biblioteca: null para o que falta, ?? para exibir um padrão.
const MULTA_POR_DIA: number = 1.5;

function multa(diasAtraso: number): number {
  return diasAtraso * MULTA_POR_DIA;
}

// Monta a linha de um empréstimo, com a multa em reais.
function linha(livro: string, diasAtraso: number): string {
  return `${livro}: ${diasAtraso} dia(s) de atraso, multa R$ ${multa(diasAtraso).toFixed(2)}`;
}

const leitora: string = 'Marina';
// A leitora não informou telefone: o valor é null, não uma string vazia.
const telefone: string | null = null;
// O aviso só existe se as multas passarem do limite.
let aviso: string | null = null;
let total: number = 0;

console.log(`Leitora: ${leitora}`);
console.log(`Telefone: ${telefone ?? 'não informado'}`);

console.log(linha('Dom Casmurro', 0));
total += multa(0);
console.log(linha('Iracema', 4));
total += multa(4);
console.log(linha('O Guarani', 2));
total += multa(2);

if (total > 5) {
  aviso = 'Pagar antes do próximo empréstimo';
}

console.log(`Total de multas: R$ ${total.toFixed(2)}`);
console.log(`Aviso: ${aviso ?? 'nenhum'}`);

Passo a passo

  1. MULTA_POR_DIA guarda o valor da multa num só lugar; o nome em maiúsculas é uma convenção comum para constantes de configuração.
  2. multa recebe os dias de atraso e devolve o valor em reais. A anotação ): number garante que ela sempre devolva um número.
  3. linha monta o texto de um empréstimo e reaproveita multa, formatando o valor com toFixed(2).
  4. telefone é const com o tipo string | null: o valor ausente é representado por null, e não por uma string vazia que poderia ser confundida com um dado digitado.
  5. aviso também é string | null, mas é let, porque pode receber um texto mais adiante. total é let e começa em 0 para acumular as multas.
  6. Cada empréstimo imprime a sua linha e soma a multa com total += multa(...).
  7. O if só atribui o aviso quando o total passa de 5. Como o total é 9, o aviso recebe o texto.
  8. As linhas de telefone e aviso usam ??: se o valor for null, aparece o texto padrão.

Por que funciona

O tipo string | null diz ao tsc que o valor pode faltar, e o operador ?? trata exatamente esse caso na hora de exibir. Com strict, escrever let aviso: string = null; seria erro: o compilador obriga a declarar a ausência no tipo, e quem usar a variável fica sabendo que precisa lidar com ela.

O total nasce em 0 e cresce a cada +=, então mudar os dias de atraso muda o total e, por consequência, a decisão sobre o aviso, sem tocar no resto do código. As multas (0, 6 e 3) são múltiplos exatos de 1,5, então a soma dá exatamente 9.

Erros comuns

  • Declarar let aviso: string = null;: no modo strict o tsc acusa TS2322 ("Type 'null' is not assignable to type 'string'"). Inclua null no tipo: string | null.
  • Usar aviso || 'nenhum' no lugar de ??: aqui a saída seria igual, mas || também troca uma string vazia '', o que pode esconder um valor real. ?? reage só a null e undefined.
  • Declarar const total: number = 0; e depois usar total += ...: erro TS2588, porque uma const não pode ser reatribuída. Acumuladores são let.
  • Testar if (total > 5) { let aviso = '...'; }: isso cria outra variável que só existe dentro do bloco, e a de fora continua null. Atribua sem let.

Outra forma de resolver

O aviso pode ser calculado numa única expressão com o ternário, o que permite const: const aviso: string | null = total > 5 ? 'Pagar antes do próximo empréstimo' : null;. É preferível quando a decisão é simples e acontece num só ponto; o if com let fica mais claro quando o aviso pode ser definido em vários lugares do programa.

Saída esperada

Leitora: Marina
Telefone: não informado
Dom Casmurro: 0 dia(s) de atraso, multa R$ 0.00
Iracema: 4 dia(s) de atraso, multa R$ 6.00
O Guarani: 2 dia(s) de atraso, multa R$ 3.00
Total de multas: R$ 9.00
Aviso: Pagar antes do próximo empréstimo

Últimas lições

Ver o arquivo completo →