Crie a estrutura inicial do projeto:
(se estiver utilizando a estrutura das aulas anteriores avance para o passo 5)
mkdir exercicio-criando-tipos && cd exercicio-criando-tipos
mkdir src public
touch src/index.ts public/index.html
npm init -y
npm install typescript --save-dev
Atualize o arquivo package.json:
"scripts": {
"tsc-watch": "tsc"
},
Crie o arquivo tsconfig.json e insira o conteúdo abaixo:
Obs.: Nossa versão alvo é o ES6 por causa de funções como Array.prototype.find() e Number.parseInt(), mas elas são opcionais, assim como outros pontos da resolução.
{
"compilerOptions": {
"target": "ES2015",
"outDir": "public/dist",
"watch": true
}
}
A partir daqui já podemos deixar o projeto rodando:
npm run tsc-watch
Adicione o conteúdo do arquivo index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Exercício 2 - Criando Novos Tipos</title>
</head>
<body>
<script src="dist/index.js"></script>
</body>
</html>
Vamos começar criando alguns tipos que vamos utilizar no script:
Obs.: Aqui aproveitamos ao máximo o uso de Aliases para deixar o código mais enxuto.
// index.ts
// Declarando os tipos
type PlanetSituation = 'Habitado' | 'Habitável' | 'Inabitável' | 'Inexplorado'
type PlanetCoordinates = [number, number, number, number]
type Planet = {
name: string
coordinates: PlanetCoordinates
situation: PlanetSituation
satellites: string[]
}
Agora vamos criar nossas funções principais, começando pela criação de planetas:
// Funções Principais
// Aqui tiramos vantagem do Alias para manipularmos
// mais precisamente nosso array de planetas
const planets: Planet[] = []
function addPlanet(name: string, coordinates: PlanetCoordinates, situation: PlanetSituation) {
// Repare que agora o autocompletar consegue nos
// sugerir as propriedades mesmo dentro da função
planets.push({
name,
coordinates,
situation,
satellites: []
})
alert(`O planeta ${name} foi salvo com sucesso.`)
}
Agora a função de encontrar um planeta que será útil posteriormente:
function findPlanet(name: string) {
const planet = planets.find(planet => planet.name === name)
// Utilizando o operador nullish coalescing podemos
// garantir que nosso retorno será um tipo Union
return planet ?? false
}
Vamos incluir a função de atualizar a situação do planeta:
// Graças ao Alias a anotação de tipos nos
// argumentos da função fica bem mais enxuta
function updateSituation(situation: PlanetSituation, planet: Planet) {
planet.situation = situation
alert(`A situação do planeta ${planet.name} foi atualizada para "${situation}".`)
}
Agora vamos às funções de adicionar e remover satélites:
// Mais uma vez o Alias se mostra muito útil para encurtar
// nosso código mas mantendo as vantagens do typescript
function addSatellite(name: string, planet: Planet) {
planet.satellites.push(name)
alert(`O satélite ${name} foi adicionado ao planeta ${planet.name}.`)
}
function removeSatellite(name: string, planet: Planet) {
// Repare o quanto o autocompletar se torna inteligente
// nesse caso apenas porque anotamos os tipos
planet.satellites = planet.satellites.filter(satellite => satellite !== name)
alert(`O satélite ${name} foi removido do planeta ${planet.name}.`)
}
Vamos também definir algumas funções auxiliares para validar as entradas do usuário. A primeira será para validar a entrada da propriedade situação:
// Funções Auxiliares
function promptValidSituation() {
let situation: PlanetSituation
let validSituation = false
while (!validSituation) {
const situationInput = prompt('Informe a situação do planeta?\\n1 - Habitado\\n2 - Habitável\\n3 - Inabitável\\n4 - Inexplorado')
switch (situationInput) {
case '1':
situation = 'Habitado'
validSituation = true
break;
case '2':
situation = 'Habitável'
validSituation = true
break;
case '3':
situation = 'Inabitável'
validSituation = true
break;
case '4':
situation = 'Inexplorado'
validSituation = true
break;
default:
alert('Situação inválida!')
break;
}
}
return situation
}
A segunda função auxiliar é para validar a entrada do nome do planeta:
Obs.: Repare que usaremos estratégias diferentes em cada uma das funções, a primeira retornando diretamente o resultado, já a segunda receberá uma função de callback para lidar com o resultado. Seus usos são apenas para ilustrar as possibilidades e vantagens do typescript e exercitar nosso raciocínio.
// Aqui anotamos os tipos da função callback
// para facilitar a sua implementação futura
function promptValidPlanet(callback: (planet: Planet) => void) {
const planetName = prompt('Informe o nome do planeta:')
const planet = findPlanet(planetName)
// Aqui podemos reparar que o VS Code nos
// avisa sobre o tipo Union de planet
if (planet) {
callback(planet)
} else {
alert('Planeta não encontrado! Retornando ao menu...')
}
}
Agora passamos às opções do menu:
// Opções do Menu
function firstMenuOption() {
const name = prompt('Informe o nome do planeta:')
const coordinateA = Number(prompt('Informe a primeira coordenada:'))
const coordinateB = Number(prompt('Informe a segunda coordenada:'))
const coordinateC = Number(prompt('Informe a terceira coordenada:'))
const coordinateD = Number(prompt('Informe a quarta coordenada:'))
// Aqui a nossa função ajuda a ter um código mais organizado
const situation = promptValidSituation()
const confirmation = confirm(`Confirma o registro do planeta ${name}?
Coordenadas: (${coordinateA}, ${coordinateB}, ${coordinateC}, ${coordinateD})
Situação: ${situation}`)
if (confirmation) {
addPlanet(name, [coordinateA, coordinateB, coordinateC, coordinateD], situation)
}
}
Vamos incluir também as opções que atualizam o planeta:
// Nessas três funções vemos como a nossa função de callback
// proporciona uma facilidade enorme na implementação
function secondMenuOption() {
// Além disso temos acesso automático às informações
// dos argumentos, nesse caso a variável planet
promptValidPlanet(planet => {
const situation = promptValidSituation()
updateSituation(situation, planet)
})
}
function thirdMenuOption() {
promptValidPlanet(planet => {
const satellite = prompt('Informe o nome do satélite a ser adicionado:')
addSatellite(satellite, planet)
})
}
function fourthMenuOption() {
promptValidPlanet(planet => {
const satellite = prompt('Informe o nome do satélite a ser removido:')
removeSatellite(satellite, planet)
})
}
Por fim, incluímos a última opção:
function fifthMenuOption() {
let list = 'Planetas:\\n'
planets.forEach(planet => {
// Repare que as tuplas são uma forma fácil de permitir a
// desestruturação com qualquer nome nas variáveis.
// As variáveis a seguir podem ter qualquer nome pois a
// tupla segue um padrão fixo.
const [a, b, c, d] = planet.coordinates
list += `
Nome: ${planet.name}
Coordenadas: (${a}, ${b}, ${c}, ${d})
Situação: ${planet.situation}
Satélites: ${planet.satellites.length}
`
planet.satellites.forEach(satellite => {
list += ` - ${satellite}\\n`
})
})
alert(list)
}
E também o menu em si:
// Menu
let userOption = 0
while (userOption !== 6) {
const menu = `Menu
1 - Registrar um novo planeta
2 - Atualizar situação do planeta
3 - Adicionar um satélite ao planeta
4 - Remover um satélite do planeta
5 - Lista todos os planetas
6 - Sair
`
userOption = Number.parseInt(prompt(menu))
switch (userOption) {
case 1:
firstMenuOption()
break
case 2:
secondMenuOption()
break
case 3:
thirdMenuOption()
break
case 4:
fourthMenuOption()
break
case 5:
fifthMenuOption()
break
case 6:
alert('Encerrando o sistema...')
break
default:
alert('Opção inválida! Retornando ao painel principal...')
break;
}
}
Agora é só testar a aplicação no navegador!