Decisões com condicionais
por Sutton AshfordEntenda o conceito
Condicionais deixam o programa escolher um caminho. O if executa um bloco quando a condição é verdadeira; else if testa outra condição se a anterior falhou, e else cobre o que sobrou. As condições são expressões boolean, montadas com comparações (===, !==, <, >=) e combinadas com && (e), || (ou) e ! (não).
Para comparar, use sempre === e !==: eles conferem valor e tipo, sem as conversões escondidas de ==. Quando a decisão é só escolher entre dois valores, o ternário condicao ? a : b cabe numa linha. Quando um mesmo valor é comparado com várias opções fixas, o switch organiza os casos, e case empilhados compartilham o mesmo bloco.
O TypeScript acrescenta duas ajudas. Com um tipo união de literais (type Status = 'pago' | 'enviado'), o tsc recusa qualquer texto fora da lista. E com o narrowing, um teste como typeof bonus === 'number' faz o compilador saber o tipo exato da variável dentro do if; num switch, o tipo never no default avisa quando algum caso ficou esquecido.
- Em
if/else if/else, só o primeiro bloco cuja condição é verdadeira roda; a ordem dos testes importa. - Compare com
===e!==;==converte tipos por baixo dos panos (0 == ''étrue). &&exige as duas condições,||aceita uma delas e!inverte umboolean.- O ternário
cond ? a : bescolhe um valor; oswitchcompara um valor com várioscase, e cadacasetermina emreturnoubreak. - Depois de
typeof x === 'number', o TypeScript trataxcomonumberdentro do bloco (narrowing).
Do conceito à prática
3 exemplos para ver a ideia funcionando.
01Ponto do bolo com if, else if e else
// Ponto do bolo: if, else if e else com comparações e operadores lógicos.
const receita: string = 'Bolo de fubá';
const temperatura: number = 180; // graus do forno
const minutosNoForno: number = 35;
const palitoSaiLimpo: boolean = true;
console.log(`Receita: ${receita}`);
// if testa uma condição; else if só é testado se a anterior falhou.
if (temperatura < 160) {
console.log('Forno frio demais: o bolo vai solar.');
} else if (temperatura > 200) {
console.log('Forno quente demais: vai queimar por fora.');
} else {
console.log('Temperatura ideal.');
}
// && exige as duas condições verdadeiras.
if (minutosNoForno >= 30 && palitoSaiLimpo) {
console.log('Pronto: pode tirar do forno.');
} else {
console.log('Ainda não: espere mais alguns minutos.');
}
// || basta uma das condições ser verdadeira.
const temOvos: boolean = true;
const temLeite: boolean = false;
if (!temOvos || !temLeite) {
console.log('Falta ingrediente: confira a lista de compras.');
}
// === compara valor e tipo, sem conversões escondidas.
if (receita === 'Bolo de fubá' && temOvos) {
console.log('Dá para assar outra fornada amanhã.');
}
O primeiro if escolhe uma entre três mensagens: o else if só é testado se a temperatura não for baixa, e o else pega o que sobrou (de 160 a 200 graus). Como 180 não cai nos dois primeiros testes, aparece "Temperatura ideal.".
Os blocos seguintes combinam condições: && exige tempo suficiente e palito limpo, || dispara o aviso se faltar ovo ou leite, e ! inverte cada boolean. A comparação receita === 'Bolo de fubá' usa ===, que confere valor e tipo sem conversões automáticas.
02Status do pedido com switch e ternário
// Status de um pedido da loja: união de literais, switch e ternário.
// O tipo Status só aceita estes quatro textos.
type Status = 'pago' | 'enviado' | 'entregue' | 'cancelado';
function mensagem(status: Status): string {
switch (status) {
case 'pago':
return 'pagamento confirmado, separando os itens';
case 'enviado':
return 'pedido a caminho';
case 'entregue':
return 'pedido entregue';
case 'cancelado':
return 'pedido cancelado, valor estornado';
}
}
// Cases empilhados compartilham o mesmo bloco.
function podeCancelar(status: Status): boolean {
switch (status) {
case 'pago':
case 'enviado':
return true;
default:
return false;
}
}
function mostrar(numero: number, status: Status): void {
// Ternário: condição ? valor se verdadeiro : valor se falso.
const cancelar = podeCancelar(status) ? 'sim' : 'não';
console.log(`#${numero} [${status}] ${mensagem(status)} | pode cancelar? ${cancelar}`);
}
mostrar(101, 'pago');
mostrar(102, 'enviado');
mostrar(103, 'entregue');
mostrar(104, 'cancelado');
// mostrar(105, 'perdido'); // erro: '"perdido"' não é atribuível a 'Status'
O tipo Status é uma união de literais: só os quatro textos listados são aceitos, e a chamada comentada com 'perdido' não passa no tsc. Em mensagem, cada case termina com return; como todos os quatro valores estão cobertos, o compilador sabe que a função sempre devolve uma string e não exige um return no fim.
Em podeCancelar, os case empilhados 'pago' e 'enviado' caem no mesmo bloco, e o default responde pelos demais. Na função mostrar, o ternário transforma o boolean em 'sim' ou 'não' numa única linha.
03Jogo de dados com narrowing e never
// Jogo de dados: narrowing com typeof e switch exaustivo com never.
// O bônus pode ser um número (+3) ou um texto ('dobro').
function aplicarBonus(rolagem: number, bonus: number | string): number {
// Dentro deste if, o TypeScript sabe que bonus é number.
if (typeof bonus === 'number') {
return rolagem + bonus;
}
// Aqui só sobrou string.
return bonus === 'dobro' ? rolagem * 2 : rolagem;
}
type Resultado = 'critico' | 'acerto' | 'erro';
function classificar(total: number): Resultado {
if (total >= 20) return 'critico';
if (total >= 10) return 'acerto';
return 'erro';
}
function narrar(resultado: Resultado): string {
switch (resultado) {
case 'critico':
return 'Golpe crítico!';
case 'acerto':
return 'Acertou o alvo.';
case 'erro':
return 'Errou...';
default: {
// Se surgir um Resultado novo sem case, o tsc acusa erro aqui.
const impossivel: never = resultado;
return impossivel;
}
}
}
function jogada(jogador: string, rolagem: number, bonus: number | string): void {
const total = aplicarBonus(rolagem, bonus);
console.log(
`${jogador}: dado ${rolagem}, bônus ${bonus}, total ${total} -> ${narrar(classificar(total))}`,
);
}
jogada('Lia', 12, 3);
jogada('Téo', 11, 'dobro');
jogada('Rui', 4, 'nenhum');
jogada('Ana', 9, 0);
O parâmetro bonus é number | string. Fora do if, não dá para somá-lo a um número; depois de typeof bonus === 'number', o TypeScript estreita o tipo e libera rolagem + bonus. Abaixo do if, que sempre retorna, só pode sobrar string, e o ternário trata o caso 'dobro'.
classificar usa if com return antecipado, que dispensa o else. Em narrar, o default atribui o valor a uma variável never: hoje esse ponto é inalcançável, mas, se alguém acrescentar 'empate' ao tipo Resultado sem criar o case, o tsc acusa o erro nessa linha.
Agora é com você
Tente resolver no rascunho antes de abrir a resposta. Os códigos não são executados nesta página.
01Frete por faixa de valorFácil
Uma loja cobra frete conforme o valor da compra: a partir de R$ 200,00 é grátis, a partir de R$ 100,00 custa R$ 10,00 e abaixo disso custa R$ 20,00. Escreva a função frete e imprima o resumo de quatro compras: 250, 120, 45.50 e 200.
- Criar
function frete(compra: number): numbercomif/else if/else. - Mostrar "frete grátis" quando o frete for zero, escolhendo o texto com o operador ternário.
- Exibir os valores com
toFixed(2); a saída deve ser exatamente a esperada.
function frete(compra: number): number {
// a partir de 200: grátis; a partir de 100: 10; abaixo: 20
return 0;
}
function resumo(compra: number): void {
// calcule o frete, escolha o texto e imprima a linha
}
resumo(250);
resumo(120);
resumo(45.5);
resumo(200);
Compra de R$ 250.00: frete grátis, total R$ 250.00
Compra de R$ 120.00: frete R$ 10.00, total R$ 130.00
Compra de R$ 45.50: frete R$ 20.00, total R$ 65.50
Compra de R$ 200.00: frete grátis, total R$ 200.00
Ver dica
Teste a faixa mais alta primeiro: se compra >= 100 vier antes, uma compra de 250 também cai nela.
Ver resposta
// Frete da loja por faixa de valor da compra.
function frete(compra: number): number {
// As faixas vão da maior para a menor: a primeira que passar vence.
if (compra >= 200) {
return 0;
} else if (compra >= 100) {
return 10;
} else {
return 20;
}
}
function resumo(compra: number): void {
const valorFrete = frete(compra);
// Ternário escolhe o texto do frete.
const textoFrete = valorFrete === 0 ? 'frete grátis' : `frete R$ ${valorFrete.toFixed(2)}`;
const total = compra + valorFrete;
console.log(`Compra de R$ ${compra.toFixed(2)}: ${textoFrete}, total R$ ${total.toFixed(2)}`);
}
resumo(250);
resumo(120);
resumo(45.5);
resumo(200);
Passo a passo
freterecebe o valor da compra e devolve o frete comonumber. A anotação): numberfaz otscreclamar se algum caminho ficar semreturn.- O primeiro teste é
compra >= 200, a faixa mais alta. Se ele falhar, oelse iftestacompra >= 100, e oelsecobre o resto. - Em
resumo, o frete é calculado uma vez e guardado emvalorFrete, para não chamar a função duas vezes. - O ternário
valorFrete === 0 ? 'frete grátis' : ...escolhe o texto: com===, a comparação é exata entre dois números. totalsoma a compra e o frete, etoFixed(2)formata os três valores em reais só na hora de imprimir.- A compra de 200 mostra o limite:
>=inclui o próprio 200 na faixa grátis.
Por que funciona
Numa cadeia if / else if / else, só o primeiro bloco verdadeiro roda. Ordenar as faixas da maior para a menor garante que cada compra caia na faixa certa sem precisar escrever intervalos completos como compra >= 100 && compra < 200: se o programa chegou ao else if, já se sabe que a compra é menor que 200.
Erros comuns
- Inverter a ordem: com
if (compra >= 100)primeiro, a compra de 250 paga R$ 10,00 de frete, porque o teste de 200 nunca é alcançado. Teste a faixa mais alta antes. - Usar
>no lugar de>=:compra > 200cobra frete da compra de exatamente 200, e a última linha sai errada. - Imprimir o valor sem formatar:
R$ ${compra}mostraR$ 45.5eR$ 250, diferente da saída esperada. UsetoFixed(2)em todos os valores em reais.
Outra forma de resolver
Como cada ramo só devolve um valor, dá para usar if com return antecipado e dispensar os else: if (compra >= 200) return 0; if (compra >= 100) return 10; return 20;. O resultado é o mesmo e fica mais curto; a cadeia com else deixa explícito que os casos são exclusivos.
Saída esperada
Compra de R$ 250.00: frete grátis, total R$ 250.00
Compra de R$ 120.00: frete R$ 10.00, total R$ 130.00
Compra de R$ 45.50: frete R$ 20.00, total R$ 65.50
Compra de R$ 200.00: frete grátis, total R$ 200.00
02A biblioteca está aberta?Médio
A biblioteca abre de segunda a sexta das 8h às 20h, no sábado das 9h às 13h e fica fechada no domingo. Crie um tipo para os dias da semana e uma função que diga se ela está aberta num dia e numa hora cheia.
- Declarar
type Diacomo união dos sete dias ('segunda'a'domingo', com acento em'terça'e'sábado'). - Criar
function estaAberta(dia: Dia, hora: number): booleancomswitch, agrupando os dias úteis emcaseempilhados. - Testar o horário com
&&, considerando que no horário de fechar (20h, 13h) ela já está fechada. - Imprimir "aberta" ou "fechada" com o operador ternário; a saída deve ser exatamente a esperada.
type Dia = 'segunda' | 'terça'; // complete com os outros dias
function estaAberta(dia: Dia, hora: number): boolean {
// use switch com os dias úteis agrupados
return false;
}
function consultar(dia: Dia, hora: number): void {
// imprima: <dia> às <hora>h: aberta | fechada
}
consultar('segunda', 19);
segunda às 19h: aberta
quarta às 7h: fechada
sexta às 20h: fechada
sábado às 12h: aberta
sábado às 15h: fechada
domingo às 10h: fechada
Ver dica
Uma hora está dentro do expediente quando é maior ou igual à abertura e menor que o fechamento.
Ver resposta
// Horário da biblioteca: switch com cases agrupados e um tipo união.
type Dia = 'segunda' | 'terça' | 'quarta' | 'quinta' | 'sexta' | 'sábado' | 'domingo';
function estaAberta(dia: Dia, hora: number): boolean {
switch (dia) {
// Dias úteis compartilham o mesmo horário: cases empilhados.
case 'segunda':
case 'terça':
case 'quarta':
case 'quinta':
case 'sexta':
return hora >= 8 && hora < 20;
case 'sábado':
return hora >= 9 && hora < 13;
case 'domingo':
return false;
}
}
function consultar(dia: Dia, hora: number): void {
const situacao = estaAberta(dia, hora) ? 'aberta' : 'fechada';
console.log(`${dia} às ${hora}h: ${situacao}`);
}
consultar('segunda', 19);
consultar('quarta', 7);
consultar('sexta', 20);
consultar('sábado', 12);
consultar('sábado', 15);
consultar('domingo', 10);
Passo a passo
type Dialista os sete textos aceitos. Com ele,consultar('sabado', 12)(sem acento) é erro notsc, e não um dia que cai silenciosamente no caso errado.- Os cinco dias úteis são
caseempilhados sem código entre eles: qualquer um dos cinco chega ao mesmoreturn. hora >= 8 && hora < 20exige as duas condições. O<(e não<=) faz a hora de fechar já contar como fechada.- O sábado tem a própria regra, e o domingo devolve
falsedireto. - Não há
default: os sete valores do tipo estão cobertos, e otscsabe que a função sempre devolve umboolean. - Em
consultar, o ternário converte obooleanem'aberta'ou'fechada'antes de imprimir.
Por que funciona
O switch compara o mesmo valor (dia) com várias opções fixas, situação em que ele é mais legível que uma cadeia de if com ||. O tipo união fecha a lista de opções: o compilador confere tanto as chamadas (nenhum dia inventado) quanto o switch (nenhum dia esquecido, senão a função poderia terminar sem return).
Erros comuns
- Esquecer um dia no
switch, por exemplo o'domingo': otscacusa TS2366 ("Function lacks ending return statement and return type does not include 'undefined'"). Cubra todos os valores ou acrescente umdefault. - Usar
hora <= 20: a consulta de sexta às 20h sai "aberta", diferente da saída esperada. - Trocar
&&por||:hora >= 8 || hora < 20é verdadeiro para qualquer hora, e a biblioteca nunca fecha nos dias úteis.
Outra forma de resolver
Uma cadeia de if resolve o mesmo: if (dia === 'domingo') return false; if (dia === 'sábado') return hora >= 9 && hora < 13; return hora >= 8 && hora < 20;. É mais curta aqui, mas o switch fica mais claro quando cada grupo de dias tem uma regra diferente e a lista de casos cresce.
Saída esperada
segunda às 19h: aberta
quarta às 7h: fechada
sexta às 20h: fechada
sábado às 12h: aberta
sábado às 15h: fechada
domingo às 10h: fechada
03Situação no boletimDesafio
Monte o boletim de cinco alunos com duas notas e uma prova final, que pode ter nota ou a marca 'ausente' (vale zero). A situação segue esta ordem: frequência abaixo de 75% reprova por falta; senão, média a partir de 7 aprova, a partir de 5 vai para recuperação e abaixo disso reprova.
- Tratar a prova final como
number | 'ausente'e obter a nota com narrowing portypeof. - Criar
type Situacaocom'aprovado','recuperacao','reprovado'e'reprovado-por-falta', e a funçãosituacao(media, frequencia)comif/else if/else. - Converter a situação em texto com
switche umdefaultque atribui o valor a uma variávelnever. - Dados: Ana 8, 7.5, 9 (90%); Bruno 6, 5, 4.5 (85%); Caio 9, 8, ausente (95%); Duda 9, 9.5, 10 (60%); Eva 3, 4, ausente (80%).
type Situacao = 'aprovado' | 'recuperacao' | 'reprovado' | 'reprovado-por-falta';
function notaDaFinal(final: number | 'ausente'): number {
// use typeof para separar os dois casos
return 0;
}
function situacao(media: number, frequencia: number): Situacao {
// a falta vem antes da média
return 'reprovado';
}
console.log('Boletim do 1º bimestre');
Boletim do 1º bimestre
Ana: notas 8, 7.5, 9 | média 8.2 | freq. 90% -> Aprovado
Bruno: notas 6, 5, 4.5 | média 5.2 | freq. 85% -> Recuperação
Caio: notas 9, 8, ausente | média 5.7 | freq. 95% -> Recuperação
Duda: notas 9, 9.5, 10 | média 9.5 | freq. 60% -> Reprovado por falta
Eva: notas 3, 4, ausente | média 2.3 | freq. 80% -> Reprovado
Ver dica
A ordem dos testes decide o resultado: a Duda tem média 9.5, mas a regra da frequência precisa ser conferida antes.
Ver resposta
// Boletim: narrowing com typeof, if/else if e switch exaustivo com never.
type Situacao = 'aprovado' | 'recuperacao' | 'reprovado' | 'reprovado-por-falta';
// A prova final pode ter nota ou a marca 'ausente'.
function notaDaFinal(final: number | 'ausente'): number {
// Depois do typeof, o TypeScript sabe que final é number.
if (typeof final === 'number') {
return final;
}
return 0; // ausência conta como zero
}
function situacao(media: number, frequencia: number): Situacao {
// A falta vem primeiro: reprova mesmo com média alta.
if (frequencia < 75) {
return 'reprovado-por-falta';
} else if (media >= 7) {
return 'aprovado';
} else if (media >= 5) {
return 'recuperacao';
} else {
return 'reprovado';
}
}
function descrever(s: Situacao): string {
switch (s) {
case 'aprovado':
return 'Aprovado';
case 'recuperacao':
return 'Recuperação';
case 'reprovado':
return 'Reprovado';
case 'reprovado-por-falta':
return 'Reprovado por falta';
default: {
// Um caso novo em Situacao sem case aqui vira erro no tsc.
const impossivel: never = s;
return impossivel;
}
}
}
function boletim(
aluno: string,
nota1: number,
nota2: number,
final: number | 'ausente',
frequencia: number,
): void {
const media = (nota1 + nota2 + notaDaFinal(final)) / 3;
const texto = descrever(situacao(media, frequencia));
console.log(
`${aluno}: notas ${nota1}, ${nota2}, ${final} | média ${media.toFixed(1)} | freq. ${frequencia}% -> ${texto}`,
);
}
console.log('Boletim do 1º bimestre');
boletim('Ana', 8, 7.5, 9, 90);
boletim('Bruno', 6, 5, 4.5, 85);
boletim('Caio', 9, 8, 'ausente', 95);
boletim('Duda', 9, 9.5, 10, 60);
boletim('Eva', 3, 4, 'ausente', 80);
Passo a passo
Situacaoé uma união de quatro literais. Ela documenta todos os resultados possíveis e permite que otscconfira oswitchmais adiante.notaDaFinalrecebenumber | 'ausente'. Dentro doif (typeof final === 'number'), o TypeScript sabe quefinalénumbere deixa devolvê-lo; fora dele, só resta'ausente', que vale zero.situacaotesta a frequência antes da média, porque a falta reprova independentemente da nota. Depois, as faixas de média vão da maior para a menor.descreverusaswitchpara transformar cada situação no texto exibido. Os valores do tipo usam hífen e não têm acento; o texto para o usuário fica num só lugar.- No
default,const impossivel: never = s;só compila porque todos os casos foram tratados antes e nada sobra paras. boletimcalcula a média com parênteses em volta da soma e chama as duas funções, encadeandodescrever(situacao(...)).- O template literal mostra
finalcomo está:9ouausente. A média aparece com uma casa decimal portoFixed(1), mas a comparação usa o valor exato.
Por que funciona
Cada função faz uma escolha: notaDaFinal separa os dois tipos possíveis da prova, situacao decide com uma cadeia em que só o primeiro teste verdadeiro vale, e descrever mapeia valores fixos com switch. O narrowing evita conversões manuais: o compilador acompanha o typeof e só deixa usar final como número no trecho em que isso é garantido.
O never transforma o switch num contrato. Se a escola criar a situação 'aprovado-com-louvor' e alguém esquecer o case, o tsc acusa "Type '"aprovado-com-louvor"' is not assignable to type 'never'" na linha do default, antes de o boletim sair com um texto faltando.
Erros comuns
- Testar a média antes da frequência: a Duda (média 9.5, frequência 60%) sai "Aprovado". Coloque
frequencia < 75no primeiroif. - Somar
finaldireto:(nota1 + nota2 + final) / 3dá erro notsc(TS2365: o operador+não se aplica anumbere a uma união que inclui texto). Passe antes pornotaDaFinal. - Comparar com o texto do usuário:
case 'Recuperação':emdescreveré erro TS2678, porque esse literal não faz parte deSituacao. Use os valores do tipo. - Esquecer um
casee manter odefaultcomnever: é exatamente o erro que ele existe para mostrar. Acrescente ocaseque falta, não remova a checagem.
Outra forma de resolver
notaDaFinal cabe numa linha com o ternário: return final === 'ausente' ? 0 : final;. A comparação com o literal também estreita o tipo, então final é number no segundo ramo. Para descrever, um objeto Record<Situacao, string> com os quatro textos substituiria o switch; ele é preferível quando cada caso só devolve um valor fixo, enquanto o switch acomoda lógica diferente em cada ramo.
Saída esperada
Boletim do 1º bimestre
Ana: notas 8, 7.5, 9 | média 8.2 | freq. 90% -> Aprovado
Bruno: notas 6, 5, 4.5 | média 5.2 | freq. 85% -> Recuperação
Caio: notas 9, 8, ausente | média 5.7 | freq. 95% -> Recuperação
Duda: notas 9, 9.5, 10 | média 9.5 | freq. 60% -> Reprovado por falta
Eva: notas 3, 4, ausente | média 2.3 | freq. 80% -> Reprovado