Mutável e Imutável no TypeScript

Mutável e Imutável no TypeScript

Este artigo explica os conceitos de mutável e imutável no TypeScript.

YouTube Video

Mutável e Imutável no TypeScript

O que é Mutável?

Mutável significa que um valor pode ser alterado. Tipos de referência, como objetos e arrays, são exemplos típicos de estruturas de dados mutáveis.

Exemplo de um Objeto Mutável

1type Person = { name: string; age: number };
2
3// Mutable Example: Object
4let person: Person = { name: "Alice", age: 25 };
5person.age = 26;
6console.log(person); // { name: "Alice", age: 26 }

Neste código, a propriedade age do objeto person é alterada de 25 para 26. Como os objetos são passados por referência, o conteúdo no endereço de memória armazenado na variável person é modificado.

Exemplo de um Array Mutável

1// Mutable Example: Array
2let numbers: number[] = [1, 2, 3];
3numbers.push(4);
4console.log(numbers); // [1, 2, 3, 4]

Neste código, o método push é usado para adicionar um novo elemento 4 ao array original. Isso modifica o array original, tornando-se uma operação mutável.

Exemplo em uma Função

1// Mutable Example: Function
2function append(arr: number[], value: number): void {
3    arr.push(value); // Modify the original array
4    console.log(arr);
5}
6
7let nums: number[] = [1, 2, 3];
8append(nums, 4);
9console.log(nums); // [1, 2, 3, 4]

Ao realizar operações mutáveis dentro de uma função, o array original também é modificado.

O que é Imutável?

Imutável significa que um valor não pode ser alterado. Tipos primitivos são fundamentalmente imutáveis.

Exemplo de um Tipo Primitivo Imutável

1// Immutable Example: String
2let str: string = "hello";
3str[0] = "H"; // Error: Index assignment is not allowed
4console.log(str); // "hello"

Tentar alterar o primeiro caractere da string str para H falha porque as strings são imutáveis.

Exemplo em uma Função

1// Immutable Example: Function
2function increment(num: number): number {
3    num++; // This modifies only the local copy of num
4    return num;
5}
6
7let number: number = 10;
8console.log(increment(number)); // 11
9console.log(number); // 10 (original number remains unchanged)

Como os números são imutáveis, operações realizadas dentro de uma função não afetam a variável original.

Operações Imutáveis em Arrays

Os arrays são mutáveis, mas criando um novo array em vez de modificar o original, é possível realizar operações imutáveis.

1// Create an array of numbers
2let numbers: number[] = [1, 2, 3];
3
4// Immutable Example: Creating a new array
5let newNumbers: number[] = [...numbers, 4];
6
7console.log(numbers); // [1, 2, 3] (original array is unchanged)
8console.log(newNumbers); // [1, 2, 3, 4] (new array with an added element)

Aqui, a sintaxe spread (...) é usada para criar um novo array newNumbers. Como o array original numbers permanece inalterado, esta é uma operação imutável.

Benefícios de Usar Estruturas de Dados Imutáveis

Maior Previsibilidade

Dados imutáveis não mudam, tornando modificações inesperadas menos prováveis e reduzindo a chance de erros.

Compatibilidade com Bibliotecas Baseadas em Imutabilidade

Bibliotecas como React e Redux são frequentemente projetadas com base em dados imutáveis, tornando o gerenciamento de estado mais fácil quando usadas adequadamente.

Tornando Objetos Imutáveis com Object.freeze

Object.freeze pode ser usado para impedir modificações em um objeto.

1// Create a frozen object (properties cannot be modified)
2const person = Object.freeze({ name: "Alice", age: 25 });
3
4// Attempt to modify a property (ignored in non-strict mode, error in strict mode)
5person.age = 26;
6
7console.log(person); // { name: "Alice", age: 25 }

No entanto, Object.freeze realiza apenas um congelamento superficial, o que significa que as propriedades de objetos aninhados permanecem mutáveis.

1// Create a frozen object with a nested object
2const user: Readonly<{ profile: { name: string } }> = Object.freeze({
3    profile: { name: "Bob" }
4});
5
6// Attempt to modify a nested property (this works because Object.freeze() is shallow)
7user.profile.name = "Charlie"; // No TypeScript error, but still mutable
8
9console.log(user.profile.name); // "Charlie" (nested object is still mutable)

Para criar um objeto completamente imutável, é necessário um congelamento profundo.

 1// Function to deeply freeze an object, making all nested objects immutable
 2function deepFreeze<T>(obj: T): Readonly<T> {
 3    Object.freeze(obj);
 4    Object.getOwnPropertyNames(obj).forEach(prop => {
 5        const value = (obj as any)[prop];
 6        if (typeof value === "object" && value !== null) {
 7            deepFreeze(value);
 8        }
 9    });
10    return obj;
11}
12
13// Create a deeply frozen object
14const user = deepFreeze({
15  profile: { name: "Bob" }
16});
17
18// Attempt to modify a nested property
19// (this will now throw an error in strict mode)
20user.profile.name = "Charlie";  // No TypeScript error, but modification is not allowed at runtime
21
22console.log(user.profile.name); // "Bob" (unchanged due to deep freeze)

Resumo

  • Dados mutáveis são modificáveis e incluem objetos e arrays.
  • Dados imutáveis são não modificáveis e incluem tipos primitivos como strings e números.
  • Usando a sintaxe spread ou métodos como map, operações de dados imutáveis podem ser realizadas.
  • Object.freeze e deepFreeze podem ser usados para impedir modificações em objetos.
  • Usar dados imutáveis ajuda a escrever um código mais previsível e menos propenso a erros.

Adotar um design imutável melhora a segurança e a legibilidade do código, portanto, use-o ao máximo!

Você pode acompanhar o artigo acima usando o Visual Studio Code em nosso canal do YouTube. Por favor, confira também o canal do YouTube.

YouTube Video