Mutowalne i Niemutowalne w TypeScript

Mutowalne i Niemutowalne w TypeScript

Ten artykuł wyjaśnia pojęcia mutowalności i niemutowalności w TypeScript.

YouTube Video

Mutowalne i Niemutowalne w TypeScript

Czym jest Mutowalne?

Mutowalne oznacza, że wartość może zostać zmieniona. Typy referencyjne, takie jak obiekty i tablice, są typowymi przykładami mutowalnych struktur danych.

Przykład Mutowalnego Obiektu

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 }

W tym przykładzie właściwość age obiektu person została zmieniona z 25 na 26. Ponieważ obiekty są przekazywane przez referencję, zawartość pod adresem pamięci przechowywanym w zmiennej person jest modyfikowana.

Przykład Mutowalnej Tablicy

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

W tym przykładzie metoda push została użyta do dodania nowego elementu 4 do oryginalnej tablicy. To modyfikuje oryginalną tablicę, co jest mutowalną operacją.

Przykład w Funkcji

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]

Podczas wykonywania mutowalnych operacji wewnątrz funkcji, oryginalna tablica również jest modyfikowana.

Czym jest Niemutowalne?

Niemutowalne oznacza, że wartość nie może zostać zmieniona. Typy prymitywne są z natury niemutowalne.

Przykład Typu Prymitywnego Niemutowalnego

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

Próba zmiany pierwszego znaku w ciągu str na H kończy się niepowodzeniem, ponieważ ciągi znaków są niemutowalne.

Przykład w Funkcji

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)

Ponieważ liczby są niemutowalne, operacje wewnątrz funkcji nie wpływają na oryginalną zmienną.

Niemutowalne Operacje na Tablicach

Tablice są mutowalne, ale przez stworzenie nowej tablicy zamiast modyfikowania oryginalnej, można uzyskać niemutowalne operacje.

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)

Tutaj składnia spread (...) jest używana do utworzenia nowej tablicy newNumbers. Ponieważ oryginalna tablica numbers pozostaje niezmieniona, jest to operacja niemutowalna.

Korzyści z używania niemutowalnych struktur danych

Zwiększona przewidywalność

Niemutowalne dane nie zmieniają się, co zmniejsza prawdopodobieństwo nieoczekiwanych modyfikacji i redukuje szansę na błędy.

Kompatybilność z bibliotekami opartymi na niemutowalności

Biblioteki takie jak React i Redux są często projektowane w oparciu o niemutowalne dane, co ułatwia zarządzanie stanem przy odpowiednim wykorzystaniu.

Tworzenie niemutowalnych obiektów za pomocą Object.freeze

Funkcja Object.freeze może być używana do zapobiegania modyfikacjom obiektu.

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 }

Jednak Object.freeze wykonuje jedynie płytkie zamrożenie, co oznacza, że właściwości zagnieżdżonych obiektów pozostają mutowalne.

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)

Aby stworzyć całkowicie niemutowalny obiekt, konieczne jest zastosowanie głębokiego zamrożenia.

 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)

Podsumowanie

  • Mutowalne dane są modyfikowalne i obejmują obiekty oraz tablice.
  • Niemutowalne dane są niemodyfikowalne i obejmują typy prymitywne, takie jak stringi i liczby.
  • Używając składni spread lub metod takich jak map, można wykonywać operacje na niemutowalnych danych.
  • Funkcje Object.freeze i deepFreeze mogą być używane do zapobiegania modyfikacjom obiektów.
  • Użycie niemutowalnych danych pomaga pisać bardziej przewidywalny i mniej podatny na błędy kod.

Przyjęcie projektu opartego na niemutowalności zwiększa bezpieczeństwo i czytelność kodu, więc korzystaj z tego w pełni!

Możesz śledzić ten artykuł, korzystając z Visual Studio Code na naszym kanale YouTube. Proszę również sprawdzić nasz kanał YouTube.

YouTube Video