Фундамент Ф1 · урок 6 з 12
Посилання проти копії
Чому в Angular tasks().push(task) «не працює», хоча масив змінився? Чому два однакові
обʼєкти не рівні між собою? Чому функція «зіпсувала» дані, які їй передали? Відповідь на все це одна —
посилання. Це, мабуть, найважливіша ідея фундаменту для Angular: на ній стоять сигнали, OnPush і вся тема
незмінності.
Примітиви копіюються, обʼєкти — ні
Коли присвоюєш число чи рядок іншій змінній, копіюється саме значення — як ксерокопія записки. Змінив копію — оригінал не постраждав.
З обʼєктами (і масивами, бо масив — теж обʼєкт) інакше. Обʼєкт живе окремо, у памʼяті, а змінна тримає лише посилання на нього — по суті, адресу. Присвоєння копіює адресу, а не обʼєкт. Дві змінні з однією адресою — як двоє людей з ключами від однієї квартири: що один переставив, те другий і побачить.
Вгадай, що виведуть обидва console.log, потім запусти.
Порівняння: === порівнює адреси
Для обʼєктів === перевіряє не вміст, а чи це той самий обʼєкт:
{ id: 1 } === { id: 1 }; // false — два різні обʼєкти з однаковим вмістом
const t = { id: 1 };
const same = t;
t === same; // true — одна адреса
[] === []; // false — те саме з масивами
Це не баг, а саме те, на що спирається Angular: перевірити «чи змінились дані» через === — миттєво,
навіть для величезного обʼєкта. Але тільки якщо зміна завжди означає новий обʼєкт.
Функції отримують посилання
Передаючи обʼєкт у функцію, ти передаєш адресу. Тож функція може змінити дані того, хто її викликав, — і це неприємний сюрприз:
function markDone(task) {
task.done = true; // змінює ОРИГІНАЛ, не копію
return task;
}
const report = { title: 'Звіт', done: false };
markDone(report);
report.done; // true — функція тихо змінила наші дані
Функція, яка змінює свої аргументи, — нечиста (урок 3). Такий код важко тестувати, і він ламає оновлення екрана в Angular.
Як зробити копію
Spread ... «розкладає» властивості обʼєкта чи елементи масиву в новий обʼєкт або масив:
const copy = { ...task }; // новий обʼєкт з тими самими полями
const updated = { ...task, done: true }; // копія з одним зміненим полем
const listCopy = [...tasks]; // новий масив з тими самими елементами
Порядок важливий: поля, написані після spread, перезаписують скопійовані. Тому { ...task, done: true }
означає «усе як у task, але done — true».
Але spread копіює лише верхній рівень. Якщо всередині є інший обʼєкт чи масив, копіюється посилання на нього — і він лишається спільним:
Для повної, глибокої копії є structuredClone(obj). Але в Angular найчастіше вона не потрібна: ти
копіюєш лише те, що змінюєш, а решту залишаєш спільним — так дешевше.
Незмінність: не змінюй, а створюй нове
Правило, яке Angular вимагає від даних стану: ніколи не мутуй — завжди створюй нову версію. Ось три операції, які ти писатимеш постійно, — і тепер у кожній зрозуміла кожна частина:
// додати задачу — новий масив зі старими елементами й новим у кінці
const added = [...tasks, newTask];
// видалити — новий масив без задачі з цим id (filter, урок 5)
const removed = tasks.filter(t => t.id !== id);
// змінити одну — map проходить усі; потрібну замінює копією зі зміною,
// решту повертає як є (тернарний — урок 2, spread — цей урок)
const updated = tasks.map(t => t.id === id ? { ...t, done: true } : t);
Останній рядок — той самий, що ти бачив в уроці про сигнали в модулі 2. Тепер він має читатися без перекладу.
Навіщо це Angular
Сигнал перевіряє, чи змінилось значення, через порівняння посилань. Компонент з OnPush перевіряє, чи змінились
вхідні дані, — теж через посилання. Якщо ти зробив tasks().push(task), масив той самий, адреса та сама —
і для Angular нічого не сталось: екран не оновиться. Якщо ж tasks.update(list => [...list, task]) —
масив новий, адреса нова, і Angular одразу бачить зміну.
Функція toggle зараз мутує вхідний масив — так у Angular не можна. Перепиши її
незмінно: вона має повертати новий масив, де в задачі з потрібним id перемкнено done,
а оригінал лишається без змін. Чотири перевірки внизу підкажуть, чи вийшло.
return list.map(t => t.id === id ? { ...t, done: !t.done } : t);const copy = originalі думка, що це копія. Це друге посилання на той самий обʼєкт.- Spread і віра в глибоку копію — вкладені обʼєкти лишаються спільними.
push,splice,sortабоobj.x = …на даних стану в Angular — екран не оновлюється.- Порівняння вмісту обʼєктів через
===— для цього порівнюють поля:a.id === b.id.
Підсумок
- Примітиви копіюються значенням, обʼєкти й масиви — посиланням: дві змінні можуть вказувати на одну річ.
===для обʼєктів порівнює адреси, а не вміст.- Копія —
{ ...obj }і[...arr], але лише верхній рівень. Глибока —structuredClone. - Angular бачить зміну лише за новим посиланням: додати —
[...list, x], видалити —filter, змінити —map+{ ...t, поле }.