Dados não Primitivos

Dados não Primitivos

Jornada Javascript: Dados não Primitivos

O conteúdo dessa lição se baseia na base dos dados não primitivos que aprendemos na 7° lição da jornada Javascript. Link abaixo!

Para acessar o link da lição, clique aqui.  

Como você já deve imaginar, o typescript também herda os 3 tipos de dados não primitivos mais conhecidos do javascript, são eles:

  • Arrays,
  • Objects,
  • Funções e Classes.

Hoje, nos aprenderemos o funcionamento de cada um deles no typescript (exceto classes, pois teremos um conteúdo exclusivo sobre isso), e veremos também sobre o funcionamento do famoso comando any 😉

Trabalhando com Arrays

No typescript, a declaração de arrays acontece de uma forma um pouco diferente, pois aqui nós também precisamos declarar os tipos de dados que o seu array vai receber.

Vamos analisar alguns exemplos:

let nomes: string[] = ['Ana', 'Maria', 'José'];//Criamos um array que só aceita strings
let idades: number[] = [23, 28, 34];//Criamos um array que só aceita números

Observe que o primeiro array só pode receber strings e o segundo só pode receber números (number).

Veja abaixo, que nós podemos usar os métodos do Javascript tranquilamente dentro de nossos arrays:

nomes.push('João');
idades.push(43);

Para criar arrays que aceitam mais de um único tipo, você vai precisar declará-los da seguinte forma:

let misturado: (string | number)[] = ['Ana', 23, 'Maria', 28, 'José', 34];

No caso do comando acima, nós estamos usando os parêntesis de modo a informar os dois tipos de dados que o array suporta, separando-os por uma barra em pé (|). Você também pode informar mais de um tipo alí dentro, por exemplo:

let misturadoDois: (string | number | null | boolean)[] = ['Ana', 23, 'Maria', 28, 'José', 34];

Apesar dessa ser uma maneira funcional de se declarar nossos arrays, a comunidade de desenvolvedores (ainda) adota uma outra sintaxe de declaração.

Usando a sintaxe <> para se declarar arrays

De uns tempos pra cá, é muito dificil encontrar projetos que ainda declaram objetos e arrays usando usando os sinais de maior (>) e menor (<).

Lá nos primordios do typescript, usávamos esses sinais para declarar nosso arrays e definir o tipo de dado que as nossas funções/classes iriam receber.

Como ainda existem projetos que usam a forma mais antiga, é interessante a gente saber como funciona, vamos dar uma olhada:

const numers: Array<number> = [1, 2, 3, 4, 5];
const names: Array<string> = ['Ana', 'Maria', 'José'];

Podemos criar um array que aceite mais de um único tipo, por exemplo:

const misturadoTres: Array<string | number> = ['Ana', 23, 'Maria', 28, 'José', 34];
let misturadoQuatro: (string | number | null | boolean)[] = ['Ana', 23, 'Maria', 28, 'José', 34];

Note que usamos a sintaxe Array<tipo> para declararmos no método antigo.

Conhecendo o tipo Any

Um dos conceitos mais controvérsios da programação em Typescript é o tipo any.

Esse tipo, faz que nossos arrays, variáveis, funções ou classes aceitem qualquer tipo de valor, sem distinção.

E isso é um GRANDE PROBLEMA, pois isso "des-tipa" os conceitos do typescript que foi feito para ser um superset completamente tipado, tipo de coisa que vai contra os princíos do typescript.

Para alguns, usar o any é como uma mão na roda, pois é muito fácil declararmos nossa lógica usando o any de modo a enviar/receber qualquer tipo de dado sem nos preocupar com a tipagem da linguagem.

Para outros, usar o any é totalmente inaceitável 😡

Mas não vai ser por isso que eu vou deixar te ensinar sobre ele, né 😂

let qualquer: any[] = ['Ana', 23, 'Maria', 28, 'José', 34];
qualquer.push(true);

//ou usando a sintaxe antiga:

let qualquerDois: Array<any> = ['Ana', 23, 'Maria', 28, 'José', 34];
qualquerDois.push(true);

Note que no comando acima nós estamos declarando um array que recebe diversos tipos de valores diferentes graças ao any.

É só o array que pode trabalhar com esse tipo? É claro que... vejamos um outro exemplo usando variáveis comuns:

let meuNome: any = "Micilini Roll";
meuNome = true;
meuNome = 25;

Além disso, podemos declarar o tipo any em retornos e recebimento de funções e classes, mas veremos o funcionamento disso mais tarde.

Quando devemos usar o any? De acordo com a minha experiência, este tipo de dado não gera burburrinho quando estamos trabalhando com arrays ou objetos que podem aceitar múltiplos tipos de dados diferentes. Principalmente aqueles advindos de uma API.

Lembrando que seu uso deve ser evitado ao máximo, pois por mais que você acredite que ele te ajuda em um primeiro momento, é só uma questão de tempo até a sua aplicação ficar cheia de anys, e posteriormente ser dificil de rastrear alguns erros na sua aplicação.

Trabalhando com funções

O fato de uma função ser um "subprograma" do programa principal, acredito que é um tipo de coisa na qual você já saiba, não é verdade? 🙂

Se ainda não sabe, corre para a lição que eu falo sobre funções no javascript (ainda dá tempo 😋).

No typescript, a declaração de uma função acontece da mesma forma como no Javascript:

function minhaFuncao(){
 console.log('Olá Mundo...');
}

minhaFuncao();

Simples, não?

A diferença se dá no recebimento dos seus parâmetros, e também no retorno dessa função (caso houver). Suponha que eu tenha uma função que recebe o nome do nosso usuário.

A variável temporária que recebe o nome do nosso usuário deverá ser TIPADA, da seguinte forma:

function faleMeuNome(nome: string){
 console.log('Olá : ' + nome);
}

let nome = "Micilini Roll";
faleMeuNome(nome);

No comando acima, nós estamos especificando que a variável temporária chamada de nome, deve receber somente o tipo e dado específicado, que no caso é uma string.

Veja outro exemplo onde estou recebendo mais de uma variável temporária dentro da função:

function faleMeusDados(nome?: string, idade?: number){
 console.log('Olá, meu nome é ' + nome + ' e tenho ' + idade + ' anos.');
}

faleMeusDados('Micilini', 23);//podemos passar valores diretamente sem a necessidade de fazer referências a variáveis

No exemplo abaixo, nós estamos declarando uma função que vai receber any:

function faleMeusDadosDois(dados: any[]){
 console.log('Olá, meu nome é ' + dados[0] + ' e tenho ' + dados[1] + ' anos.');
}

faleMeusDadosDois(['Micilini', 23]);//passamos um array com os valores

Já neste exemplo abaixo, nós estamos criando uma outra função que vai receber um array de números, e fazer o somatório dos mesmos.

function somaArray(numeros: number[]){
 let soma: number = 0;
 for(let i:number = 0; i < numeros.length; i++){
 soma += numeros[i];
 }
 return soma;
}

console.log(somaArray([1, 2, 3, 4, 5]));//15

Incrívelmente fácil, não?

Falando agora sobre o retorno de uma função, basta que a variável que vai chamar a função (e receber seu valor), tenha declarado o tipo correspondente, observe:

function saudacao(nome: string): string{
 return 'Olá, ' + nome;
}

const boasVindas: string = saudacao('Micilini');//Olá, Micilini

Se o tipo de retorno fosse um array, a variável que o chama deveria ser do tipo array, se fosse number a variável de retorno deveria ser do tipo number, e assim sucessivamente.

Note que no comando acima, não só estamos definindo a variável boasVindas (que recebe o retorno da função) como uma string, como também existe o :string antes da abertura das chaves da função, o que indica que aquela função retorna uma string.

Vejamos mais exemplos:

function somaDoisEmCimaDois(numero: number): number{
 return numero + 2;
}

const resultado: number = somaDoisEmCimaDois(2);//4

No caso da função acima, estamos recebendo um número e somando com dois.

Veja agora uma função de retorno usando o tipo any:

function qualquerCoisa(): any{
 return 'Olá, mundo!';
}

const retorno: any = qualquerCoisa();

Funções com retornos do tipo void

No Typescript, uma função que tem um tipo de retorno void significa que ela não retorna nenhum valor explicitamente.

O que é o mesmo que declarar uma função como estamos acostumados a fazer com o Javascript:

function naoRetornoNada(): void{
 console.log('Olá, mundo!');
}

//É o mesmo que...

function naoRetornoNadaDois(){
 console.log('Olá, mundo!');
}

Em outras palavras, ela pode realizar operações internas, mas não produz um valor para ser utilizado após a sua execução.

Funções com retornos do tipo never

Ainda com o typescript, nós podemos ter funções que retornam o tipo never.

Ele indica que a função nunca termina de executar, e portanto, não produz um valor de retorno ou um término normal.

function erro(mensagem: string): never{
 throw new Error(mensagem);
}

erro("Vai dar ruim, cuidado...");

Isso geralmente é usado para funções que lançam exceções ou entram em loops infinitos.

Declarando funções anônimas

A declaração de funções anônimas em typescript, segue a mesma lógica das funções normais, observe:

const soma = function(a: number, b: number): number{
 return a + b;
}

console.log(soma(2, 3));//5

Por que nós não declararmos o tipo da variável soma como number neste caso?

Se você declarasse soma como number:

const soma: number = function(a: number, b: number): number{
 return a + b;
}// Erro: Type '(...args: any[]) => number' is not assignable to type 'number'.

console.log(soma(2, 3));// Erro: Type '(...args: any[]) => number' is not assignable to type 'number'.

Você estaria dizendo ao TypeScript que soma é um número, enquanto na verdade ela é uma função.

Isso criaria um erro de tipo "is not assignable", porque você está tentando atribuir uma função a uma variável que deveria armazenar um número.

Claro que o que vou dizer aqui não é necessário... mas se mesmo assim você quisesse declarar um tipo para soma, você poderia fazer algo como:

const soma: (a: number, b: number) => number = function(a: number, b: number): number {
 return a + b;
}

console.log(soma(2, 3)); // 5

Ali nos estamos declarando que soma vai receber uma função com dois parâmetros que serão passado, e que o resultado final vai ser o retorno de um valor númerico (number).

Trabalhando com Objetos

Assim como acontece com as funções, nós também precisamos declarar os tipos que iremos receber em nosso objetos, de modo a definir quais tipos as nossas propriedades vão possuir.

Vejamos como isso funciona:

const pessoaObjeto: {nome: string, idade: number} = {
 nome: 'Micilini',
 idade: 23
};//Declarando uma variável como um objeto

No comando acima criamos um novo objeto onde definimos que o nome é uma string e a idade é um number.

function mostraDados(pessoa: {nome: string, idade: number}){
 console.log('Olá, meu nome é ' + pessoa.nome + ' e tenho ' + pessoa.idade + ' anos.');
}

mostraDados({nome: 'Micilini', idade: 23});//Olá, meu nome é Micilini e tenho 23 anos.

Note que estamos enviando um objeto que conta com as especificações declaradas nas variáveis temporárias da função mostraDados.

Veja um outro exemplo, onde estamos enviando dados e recebendo um objeto:

function criarPessoaObjeto(nome: string, idade: number): {nome: string, idade: number}{//Aqui estamos dizendo ao TS que estamos retornando um objeto!
 return {nome: nome, idade: idade};
}

console.log(criarPessoaObjeto('Micilini', 23));//{nome: 'Micilini', idade: 23}

Lembrando que nós também podemos declarar um objetivo com o tipo any:

const qualquerObjeto: {nome: any, idade: any} = {
 nome: 'Micilini',
 idade: 23
};//Declarando uma variável como um objeto (usando ANY)

Declarando objetos opcionais

Um detalhe importante é que nem sempre os objetos possuem todas suas propriedades que poderiam possuir, como é o caso de declararmos um objeto que inicialmente deveria ter dois valores, enquanto na verdade só um está sendo armazenado, por exemplo:

const pessoaObjetoOpcional: {nome: string, idade?: number} = {//Quando colocamos o ponto de interrogação, estamos dizendo que o campo é opcional
 nome: 'Micilini'
};//Aqui a idade é opcional

Observe que usamos o ponto de interrogação (?) após a declaração da variável idade, indicando que ela é um valor opcional.

🛑 Lembrando que o primeiro argumento nunca pode ser opcional!🛑

Você também pode declarar variáveis temporárias de forma opcional dentro de suas funções da seguinte forma:

function dadosOpcionais(nome: string, idade?:number){
 if(idade !== undefined){
 console.log('Olá, meu nome é ' + nome + ' e tenho ' + idade + ' anos.');
 }else{
 console.log('Olá, meu nome é ' + nome + ' e não informei a idade.');
 }
}

dadosOpcionais('Micilini', 23);//Olá, meu nome é Micilini e tenho 23 anos.

//Ou, um exemplo um pouco mais complexo...

function mostraDadosOpcionais(pessoa: {nome: string, idade?: number}){
 if(pessoa.idade){
 console.log('Olá, meu nome é ' + pessoa.nome + ' e tenho ' + pessoa.idade + ' anos.');
 }else{
 console.log('Olá, meu nome é ' + pessoa.nome + ' e não informei a idade.');
 }
}

mostraDadosOpcionais({nome: 'Micilini', idade: 23});//Olá, meu nome é Micilini e tenho 23 anos.

Observação: Quando declaramos um valor opcional no Typescript, ele não nos ajuda mais, o que faz com que ele deixe de controlar o valor que estamos recebendo. Por isso, temos a necessidade de usar a condicional if...else para fazer nossas validações (por conta própria).

Declarando arrays com objetos em Typescript

No Typescript, você pode criar um array de objetos de forma bem fácil da seguinte forma:

const pessoas: {nome: string, idade: number}[] = [
 {nome: 'Ana', idade: 23},
 {nome: 'Maria', idade: 28},
 {nome: 'José', idade: 34}
];

Lembrando que se desejar, podemos passar o any dentro da declaração dos nosso tipos 😉

Arquivos da lição

Os arquivos desta lição podem ser encontrados no repositório do GitHub por meio deste link.

Conclusão

Nesta lição você aprendeu a criar funções, objetos e arrays no Typescript por meio da tipagem, e ainda teve a oportunidade de usar o tipo any!

Até a próxima lição 🙃