#12 - Робота з масивами, колекціями, Map, Set, WeakMap, WeakSet, генераторами та ітераторами в TypeScript

Так як ми уже розібрали основи та синтаксис в TypeScript, я відразу вам буду показувати приклади коду.

Масиви в TS:

let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];

numbers.push(4);
strings.forEach((str) => console.log(str.toUpperCase()));

// на виході отримаєте 
"use strict";
let numbers = [1, 2, 3];
let strings = ['a', 'b', 'c'];
numbers.push(4);
strings.forEach((str) => console.log(str.toUpperCase()));

Як бачите з прикладу у вас є 2 типи запису. Перший це коли ви вказуєте тип а після знак масиву - number[], а інший коли ви вказуєте Array, та його тип <string>

У випадку, коли вам потрібно вказати відразу декілька типів, ви можете використовувати суміжні типи:

let mixed: (string | number)[] = ['hello', 42];

Колекції: Map та Set

Якщо ви вперше стикаєтеся з map set, то це нормально. Тому що вони хоча і були додані в JS дуже давно, але широкого використання або популярності не отримали. По факту Map це обьект, в якому ви можете задавати будь які ключі, навіть обʼєктом, та задавати значення. Це може бути користно при динаміці, та реактивності. Просто подивіться приклад (на нативному JS):

const map = new Map();

map.set("name", "Ivan");        // ключ — string
map.set(42, "The answer");      // ключ — number
map.set({ x: 10 }, "object");   // ключ — об'єкт

console.log(map.get("name")); // Ivan
console.log(map.get(42));     // The answer

В мапах можна використовувати for of, а також зберігається порядок вставки, що може бути користним. І тепер давайте перейдем до TS, щоб зрозуміти як це використовується в TS.

Map<K, V> — асоціативний масив

const userRoles: Map<string, string> = new Map();

userRoles.set("Alice", "Admin");
userRoles.set("Bob", "User");

console.log(userRoles.get("Alice")); // "Admin"

З прикладу видно, що ви задаєте типи для ключа та значення.

Ітерація по Map

for (const [user, role] of userRoles) {
  console.log(`${user} => ${role}`);
}

Set<T> — множина (унікальні значення)

const uniqueNumbers: Set<number> = new Set([1, 2, 3, 3, 4]);

uniqueNumbers.add(5);
console.log(uniqueNumbers.has(3)); // true

WeakMap та WeakSet

Вгорі я пояснював що так map та set, тут в принципі теж саме, але у WeakMap та WeakSet є декілька відмінностей. WeakMap і WeakSet працюють тільки з об’єктами. Це зроблено для того, щоб не заважати збору сміття (garbage collection). Наприклад:

const secretData = new WeakMap();

const user = { name: "Alice" };

secretData.set(user, "Секретний токен");

console.log(secretData.get(user)); // "Секретний токен"

Якщо user стане недоступним (наприклад, його більше не використовують у коді), дані в WeakMap також будуть автоматично видалені. Також weakmap не перебираються (for..of, .keys(), .entries() — недоступні), та не мають .size — працюють тільки з об’єктами (не примітивами).

Ви можете задати логічне питання, а навіщо вони взагалі потрібні? Вони використовуються для кешування, як показано в прикладі вгорі з секретним токеном. Або для того, щоб не зберігати мертві об'экти, гарбажі )

Ось простий приклад використання для кешування, так вам буде більше зрозуміло:

const cache = new WeakMap<object, number>();

function heavyCalculation(obj: object): number {
  if (cache.has(obj)) {
    return cache.get(obj)!;
  }

  const result = Math.random(); // умовно важка операція
  cache.set(obj, result);
  return result;
}

const myObj = {};
console.log(heavyCalculation(myObj)); // обчислюється і кешується
console.log(heavyCalculation(myObj)); // береться з кешу

Так тепер перейдомо саме до TS прикладів:

WeakMap<object, value>

const privateData = new WeakMap<object, string>();

const obj = {};
privateData.set(obj, "секрет");

console.log(privateData.get(obj)); // "секрет"

WeakSet<object>

const visited = new WeakSet<object>();

const page = {};
visited.add(page);

console.log(visited.has(page)); // true

Генератори

Генератори — це функції, які можуть призупинити своє виконання та відновити його пізніше.

function* idGenerator() {
  let id = 0;
  while (true) {
    yield id++;
  }
}

const gen = idGenerator();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1

Ітератори

Ітератор — це об’єкт з методом next(), який повертає { value, done }.

const iterableObject = {
  data: [10, 20, 30],
  [Symbol.iterator]() {
    let i = 0;
    return {
      next: () => {
        if (i < this.data.length) {
          return { value: this.data[i++], done: false };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

for (const item of iterableObject) {
  console.log(item);
}

Комбінуємо генератори з колекціями

function* collectionPrinter<T>(items: Iterable<T>) {
  for (const item of items) {
    yield `Item: ${item}`;
  }
}

const set = new Set(["apple", "banana"]);
for (const item of collectionPrinter(set)) {
  console.log(item);
}

І на цьому мабуть урок все, дякую вам за увагу ) 🎉