Aguarde...

6 de agosto de 2021

Introdução aos tipos de objeto no TypeScript

Introdução aos tipos de objeto no TypeScript

Em JavaScript, os objetos estão entre as formas mais populares de trabalhar e transmitir dados. No TypeScript, existe um tipo especial chamado “tipo de objeto” criado exclusivamente para objetos. Este tutorial ajudará você a entender quais são os tipos de objeto no TypeScript e como trabalhar com eles.

Tipos de objetos em um resumo

Em JavaScript, existem basicamente dois tipos de valores. O primeiro tipo são tipos de dados primitivos . Esses são os oito tipos de dados básicos, alguns dos quais você trabalhará com frequência. Esses tipos de dados incluem string, número, booleano, nulo, símbolo e assim por diante. Além desses tipos de dados primitivos, existe o segundo tipo de valores.

Este segundo tipo de valores são objetos. Em JavaScript, você pode distinguir rapidamente entre um tipo de dados primitivo e um objeto olhando para o valor. Se o valor em si tiver alguma propriedade, ele é um objeto. Caso contrário, é um dos oito tipos de dados primitivos. Cada um desses tipos também tem um tipo correspondente no TypeScript.

Isso se aplica a objetos também. No TypeScript, há um novo tipo chamado tipo de objeto. Este tipo se aplica a qualquer valor que tenha algumas propriedades, pelo menos uma. Este novo tipo visa facilitar o trabalho com objetos, bem como a sua anotação.

Tipos de objetos anônimos

TypeScript permite definir dois tipos de tipos de objetos. O primeiro tipo é anônimo. Isso ocorre quando você define um objeto para um objeto específico sem usar o tipo ou uma interface. Um exemplo de tipo de objeto anônimo pode ser um parâmetro de função. Digamos que você tenha uma função que aceita um objeto como parâmetro.

Se você deseja definir o tipo de objeto para este parâmetro de objeto como anônimo, você o definirá na definição da função. Você define quais propriedades o objeto deve ter. Para cada propriedade, você também define qual é o tipo do valor da propriedade.

// Define a function with anonymous object type:
function myFunc(user: { username: string, email: string }) {
  return `user: ${user.username}, email: ${user.email}`
}

No exemplo acima, você definiu o parâmetro do objeto chamado de user. O tipo de objeto anônimo deste parâmetro diz que o objeto tem duas propriedades: usernameemail. Ambas as propriedades são do tipo string e ambas são obrigatórias.

Tipos de objetos nomeados

A segunda maneira de definir os tipos de um objeto é usando um alias de tipo ou uma interface. Nesse caso, você usa um dos dois para definir a forma do objeto. Quando você deseja anotar um objeto com esta forma, você faz referência ao apelido de tipo ou interface. O TypeScript usará o alias ou interface para inferir tipos para todas as propriedades do objeto.

// No.1: type alias
// Create a type alias for user object:
type User = {
  username: string;
  email: string;
}

// No.2: interface
// Create am interface for user object:
type User = {
  username: string;
  email: string;
}

// Use the type alias or interface to annotate user parameter:
function myFunc(user: User) {
  return `user: ${user.username}, email: ${user.email}`
}

A estrutura do próprio tipo de objeto é a mesma. Existem ainda duas propriedades, de um tipo string. A diferença é que agora o tipo de objeto é definido fora da função ou local onde é utilizado, independentemente se você quiser.

Tipo de objeto nomeado e anônimo e reutilização

Os tipos de objetos nomeados têm um grande benefício que é a reutilização de seu código. Ao definir os tipos de objeto como nomeados, você pode usá-los quantas vezes quiser. Se você também exportá- los, também poderá usá-los em qualquer lugar que desejar. Escreva uma vez, use em qualquer lugar, a qualquer hora. Você não pode fazer isso com tipos anônimos.

// Define the type alias for Human object once:
type Human = {
    name: string;
    age: number;
}

// Use Human type alias for one function:
function getUser(user: Human) {
  return `name: ${user.name}, age: ${user.age}`
}

getUser({ name: 'Tim', age: 44 })
// Output:
// 'name: Tim, age: 44'

// Use Human type alias for another function:
function getUsers(users: Human[]) {
  const usersNames = users.map(user => user.name)

  return usersNames
}

getUsers([{
  name: 'Joe',
  age: 21
}, {
  name: 'Jack',
  age: 36
}, {
  name: 'Samantha',
  age: 27
}])
// Output:
// [ 'Joe', 'Jack', 'Samantha' ]

Como o tipo de objeto anônimo não tem nome, você não pode fazer referência a ele em nenhum outro lugar do código. Se você quiser reutilizar a forma que ele define, você deve escrevê-lo novamente. Esse é um dos motivos pelos quais os desenvolvedores de TypeScript usam tipos de objetos nomeados com mais freqüência do que anônimos. No entanto, isso não significa que você nunca deve usar o tipo de objeto anônimo.

Uma boa regra é pensar no objeto e na probabilidade de você usar sua forma novamente. Se for provável que você trabalhe com sua forma, ou semelhante, pode ser uma boa ideia criar um alias de tipo ou uma interface. Então, sempre que trabalhar com aquela forma específica, você fará referência ao alias ou à interface.

Isso tornará muito mais fácil fazer alterações enquanto você trabalha. Você terá que mudar apenas um lugar, o alias ou a interface. Depois de fazer a alteração, ela se propagará em todos os lugares em que você usar o alias ou a interface. Compare isso com pesquisar todas as ocorrências dessa forma específica em seu código e atualizá-las.

Isso também o ajudará a manter a probabilidade de bugs no mínimo. Quando você atualizar o alias ou a interface, o TypeScript poderá avisá-lo imediatamente se você precisar alterar algum código para que o código reflita a nova forma. Isso não acontecerá com o tipo de objeto anônimo porque não há uma fonte única de verdade que o TypeScript possa usar.

Por outro lado, se não for provável que você trabalhe com essa forma, ou outra semelhante, o tipo de objeto anônimo fará o trabalho.

Modificadores de tipo e propriedade de objeto

Ao definir um tipo de objeto, anônimo ou nomeado, todas as propriedades são obrigatórias e podem ser alteradas. O TypeScript permite que você altere isso com a ajuda de modificadores de propriedade.

Propriedades opcionais do objeto

Há uma diferença entre um objeto que pode ter algumas propriedades e um objeto que deve ter algumas propriedades. Quando você cria um tipo de objeto que especifica algumas propriedades, o TypeScript espera encontrar essas propriedades no objeto que você anotou com aquele tipo de objeto.

Se você esquecer de definir todas essas propriedades no objeto, o TypeScript reclamará. Junto com isso, o TypeScript também espera encontrar apenas as propriedades que você definiu. Não vai esperar nenhum outro. Na verdade, ele também reclamará se encontrar algumas propriedades adicionais. Existem duas maneiras de sair disso.

A primeira maneira é criar várias variações do tipo de objeto para cobrir vários casos de uso. Isso pode funcionar para alguns casos, quando você altera a forma do objeto. No entanto, criar uma nova variante apenas para tornar uma propriedade opcional é uma loucura. Em vez disso, o que você pode fazer é informar ao TypeScript que algumas propriedades são opcionais.

Isso também informará ao TypeScript que a propriedade pode não ser definida sempre. E, se de fato não estiver definido, deve reclamar. Bem, a menos que você realmente tente usar a propriedade. Você pode fazer isso, tornando algumas propriedades opcionais, colocando um símbolo de ponto de interrogação ( ?) logo após o nome da propriedade no tipo de objeto.

// Create object type with optional properties:
type Animal = {
  name: string;
  species: string;
  numberOfLegs?: number; // This property is optional (the '?' after the property name)
  wingSpan?: number; // This property is optional (the '?' after the property name)
  lengthOfTail?: number; // This property is optional (the '?' after the property name)
}

// This will work:
const dog: Animal = {
  name: 'Jack',
  species: 'dog',
  numberOfLegs: 4,
  lengthOfTail: 30
}

// This will work:
const bird: Animal = {
  name: 'Dorris',
  species: 'pelican',
  wingSpan: 1.83,
}

// This will work:
const fish: Animal = {
  name: 'Nemo',
  species: 'fish'
}

// This will not work:
const spider: Animal = {
  name: 'Joe'
  // The "species" property is required, but missing.
}
// TS error: Property 'species' is missing in type '{ name: string; }' but required in type 'Animal'.

Propriedades de objeto somente leitura

O segundo modificador de propriedade é readonly. Este modificador ajuda a especificar propriedades cujos valores não devem ser alterados após inicializá-los. Observe que esse modificador funciona apenas em TypeScript. Se você marcar alguma propriedade como somente leitura e, posteriormente, tentar alterá-la, o TypeScript reclamará com um erro.

No entanto, isso não impedirá que o JavaScript execute essa alteração. Para JavaScript, não existe uma propriedade somente leitura, pelo menos não agora. Você pode especificar uma propriedade como somente leitura, colocando a readonlypalavra – chave logo antes da propriedade no tipo de objeto.

// Create object type with optional properties:
type User = {
  readonly name: string; // Make "name" readonly
  readonly email: string; // Make "email" readonly
  password: string;
  role: 'admin' | 'user' | 'guest';
}

// This will work:
const jack: User = {
  name: 'Jack',
  email: 'jack@jack.com',
  password: '1234_some_pass_to_test_56789',
  role: 'guest'
}

// This will work:
// Try to change value of property "role" on "jack" object:
jack.role = 'user'

// This will not work:
// Try to change value of readonly property "email" on "jack" object:
jack.email = 'jack@yo.ai'
// TS error: Cannot assign to 'email' because it is a read-only property.

Tipos de objeto e assinaturas de índice

Até agora, trabalhamos com objetos nos quais conhecíamos todas as propriedades de antemão. Isso pode não ser verdade todas as vezes. Você pode se encontrar em situações em que saberá apenas que tipo de propriedade e que tipo de valor esperar. No entanto, você pode não saber o nome exato da propriedade.

No TypeScript, isso não é um problema graças às assinaturas de índice. Com assinaturas de índice, você pode especificar o tipo de uma propriedade que espera junto com o tipo de seu valor. Isso lhe dá muita flexibilidade porque, desde que os dois tipos estejam corretos, o TypeScript não reclamará de nada.

Quando quiser usar a assinatura do índice, você deve se lembrar de usar uma sintaxe ligeiramente diferente para definir as propriedades. Normalmente, você definiria alguma propriedade “X”, adicionaria dois pontos e, em seguida, adicionaria algum tipo para seu valor. Isso informa ao TypeScript que há uma propriedade específica “X” no objeto. Acontece que não conhecemos esse “X”.

Para superar isso com a assinatura do índice, você deve envolver a propriedade entre colchetes e adicionar algum tipo. Este tipo indica o tipo de propriedade em si. Os tipos permitidos para assinaturas de índice são string e número. O resto é o mesmo. O que se segue a seguir são dois pontos e algum tipo para o valor.

// Create object type with index signature:
type StringKey = {
  // The property will be a type of string:
  [key: string]: string;
}

// Create another object type with index signature:
type NumberKey = {
  // The property will be a type of number:
  [index: number]: string;
}

// This will work:
const user: StringKey = {
  // Property is always a string.
  firstName: 'Jack',
  lastName: 'Doe',
}

// This will work:
const bookshelf: StringKey = {
  // Property is always a number.
  1: 'Hackers and Painters',
  2: 'Blitzscaling',
}

// This will not work:
const languages: NumberKey = {
  // Properties are strings, not numbers.
  one: 'JavaScript',
  two: 'TypeScript',
}
// TS error: Type '{ one: string; two: string; }' is not assignable to type 'NumberKey'.
// Object literal may only specify known properties, and 'one' does not exist in type 'NumberKey'.

// This will also not work:
const pets: StringKey = {
  // Properties are strings,
  // but the values are numbers and not strings.
  dog: 1,
  cat: 2,
}
// TS error: Type 'number' is not assignable to type 'string'.

Assinaturas de índice somente leitura

As assinaturas de índice também permitem que você use a readonlypalavra-chave para especificar propriedades somente leitura.

// Create object type with index signature and readonly property:
type ReadonlyStringKey = {
  // The property will be a type of string and a readonly:
  readonly [key: string]: string | number;
}

// Create new object with shape of "ReadonlyStringKey":
const cat: ReadonlyStringKey = {
  name: 'Suzzy',
  breed: 'Abyssinian Cat',
  age: 2
}

// This will not work:
// Try to change the value of "name" property on "cat":
cat.name = 'Vicky'
// TS error: Index signature in type 'ReadonlyStringKey' only permits reading.

// Try to change the value of "age" property on "cat":
cat.age = 1
// TS error: Index signature in type 'ReadonlyStringKey' only permits reading.

Conclusão: Introdução aos Tipos de Objetos no TypeScript

Os objetos são parte fundamental do JavaScript. Os tipos de objeto do TypeScript também podem torná-los seguros para o tipo. Os tipos de objeto também podem facilitar o trabalho com objetos em geral. Espero que este tutorial tenha ajudado você a aprender o que são tipos de objetos anônimos e nomeados no TypeScript e como usar modificadores de propriedade e assinaturas de índice.

Postado em Blog
Escreva um comentário