Variáveis e tipos básicos
por Ellery BlackwoodEntenda 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 ??.
- Use
constpor padrão eletsó para o que será reatribuído; evitevar. - 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'. nulleundefinedindicam ausência; o tipo precisa permitir, como emstring | null.a ?? bdevolvebsó quandoaénullouundefined;||também troca0,''efalse.
Do conceito à prática
3 exemplos para ver a ideia funcionando.
01const e let no estoque da loja
// 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
// 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
// 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.
- Declarar o nome do jogador com
constevidasepontoscomlet, todos com anotação de tipo. - Atualizar os valores com
+=,-=,*=e++, sem escrever os resultados à mão. - Imprimir as seis linhas com
console.loge template literals, exatamente como na saída esperada.
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
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
jogadoréconstporque o nome não muda durante a partida;vidasepontossãoletporque recebem valores novos a cada evento.- As anotações
: stringe: numberdeixam explícito o tipo de cada variável. Mesmo comlet, o tipo fica fixo: só o valor muda. pontos += 50é o mesmo quepontos = pontos + 50, só que mais curto. Na fase 2,+=e-=atualizam as duas variáveis.pontos *= 2multiplica e guarda de volta, transformando 80 em 160.vidas++soma 1 à variável; é a forma usual de contar "mais um".- Cada
console.logrepete 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 fazerpontos += 50: otscacusa TS2588 ("Cannot assign to 'pontos' because it is a constant") e o Node lançaTypeError: Assignment to constant variable. Uselet. - 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, comovidas = 'duas';: erro TS2322 notsc. Oletpermite 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.
- Converter os textos com
Number()e guardar os resultados em constantes anotadas comonumber. - Mostrar com
typeofo 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'comNumber.isNaNe guardar o resultado numa constanteboolean.
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
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
- Os quatro campos são
string, como chegariam de um formulário ou de um arquivo de texto. Number(quantidadeTexto)converte'3'em3, eNumber(precoTexto)converte'19.90'em19.9. As constantes recebem a anotaçãonumber, que é o tipo devolvido porNumber().- As duas primeiras linhas usam
typeofantes e depois da conversão, mostrando que o valor passou destringparanumber. - O subtotal é
3 * 19.9, o desconto é 10% do subtotal e o total é a diferença. Os parênteses em(subtotal * cupom) / 100deixam a ordem da conta clara. toFixed(2)aparece só na exibição. Ele arredonda para duas casas e devolve texto, por isso não é usado nas contas.Number('dois')dáNaN;Number.isNaNdevolvetrue, 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 + precoTextonão dá erro notsc(+também junta textos), mas resulta em'319.90'. Converta antes. - Testar
NaNcom igualdade:Number('dois') === NaNé semprefalse, porqueNaNnão é igual nem a si mesmo. UseNumber.isNaN. - Anotar
const subtotal: number = (quantidade * preco).toFixed(2);: erro TS2322, poistoFixeddevolvestring.
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.
- Criar
function multa(diasAtraso: number): numbere acumular o total emlet total: number = 0com+=. - Declarar
telefoneeavisocom o tipostring | null, começando emnull;avisorecebe 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).
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}`);
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
MULTA_POR_DIAguarda o valor da multa num só lugar; o nome em maiúsculas é uma convenção comum para constantes de configuração.multarecebe os dias de atraso e devolve o valor em reais. A anotação): numbergarante que ela sempre devolva um número.linhamonta o texto de um empréstimo e reaproveitamulta, formatando o valor comtoFixed(2).telefoneéconstcom o tipostring | null: o valor ausente é representado pornull, e não por uma string vazia que poderia ser confundida com um dado digitado.avisotambém éstring | null, mas élet, porque pode receber um texto mais adiante.totalélete começa em0para acumular as multas.- Cada empréstimo imprime a sua linha e soma a multa com
total += multa(...). - O
ifsó atribui o aviso quando o total passa de 5. Como o total é 9, o aviso recebe o texto. - As linhas de telefone e aviso usam
??: se o valor fornull, 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 modostrictotscacusa TS2322 ("Type 'null' is not assignable to type 'string'"). Incluanullno 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ó anulleundefined. - Declarar
const total: number = 0;e depois usartotal += ...: erro TS2588, porque umaconstnão pode ser reatribuída. Acumuladores sãolet. - Testar
if (total > 5) { let aviso = '...'; }: isso cria outra variável que só existe dentro do bloco, e a de fora continuanull. Atribua semlet.
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