effect и реакция на изменения
effect — это наблюдатель: он выполняет побочное действие каждый раз, когда меняется любой прочитанный им сигнал.
«computed отвечает на вопрос "что", effect — на вопрос "что сделать, когда". Логирование, синхронизация, аналитика — его территория».
Сигналы хранят, computed выводят, а кто реагирует на изменения действием? Это effect(). Он запускает функцию, отслеживает, какие сигналы она читает, и перезапускает её при каждом их изменении. Идеально для побочных эффектов: записать в localStorage, отправить аналитику, синхронизировать с внешним миром.
import { Component, signal, effect } from '@angular/core';
@Component({
selector: 'app-theme',
standalone: true,
template: `<button (click)="toggle()">Сменить тему</button>`,
})
export class ThemeComponent {
theme = signal<'light' | 'dark'>('light');
constructor() {
effect(() => {
// выполнится сразу и при каждой смене theme()
document.body.dataset['theme'] = this.theme();
localStorage.setItem('theme', this.theme());
});
}
toggle() {
this.theme.update(t => (t === 'light' ? 'dark' : 'light'));
}
}
Эффект запускается один раз при создании и затем при каждом изменении прочитанных сигналов. Зависимости определяются автоматически: эффект «подписан» ровно на те сигналы, которые реально прочитал.
Как работает под капотом
При первом запуске эффект записывает, какие сигналы он читал, — это его зависимости. Когда любой из них меняется, планировщик Angular помечает эффект на перезапуск (он выполняется асинхронно, после текущего цикла). Эффект может вернуть функцию очистки onCleanup, которая вызовется перед следующим запуском — удобно для таймеров и подписок.
effect(() => { читает theme() })
|
запомнил зависимость: theme
|
theme.set('dark')
|
планировщик: эффект грязный -> перезапуск (асинхронно)
|
(перед перезапуском) onCleanup() прошлого запуска
Запускаемая врезка: паттерн «наблюдатель» (observer)
Эффект — это, по сути, наблюдатель. Смоделируем его. «Попробуй сам ▶».
// реактивный эффект: перезапускается при изменении источника
function reactive(initial) {
let value = initial;
const watchers = [];
return {
get: () => value,
set: (v) => { value = v; watchers.forEach(w => w()); },
effect: (fn) => { watchers.push(fn); fn(); }, // запуск сразу
};
}
const log = [];
const theme = reactive('light');
theme.effect(() => {
log.push('применяю тему: ' + theme.get()); // побочное действие
});
theme.set('dark'); // эффект перезапустился
theme.set('light');
console.log(log);
// ["применяю тему: light", "применяю тему: dark", "применяю тему: light"]
Частые ошибки
- Менять сигналы внутри эффекта без необходимости — легко создать бесконечный цикл.
- Использовать effect там, где нужен computed. Если вы выводите значение — это computed, а не effect.
- Забыть очистку. Таймеры и подписки в эффекте надо снимать через
onCleanup.
Best practices
- Применяйте effect для синхронизации с «внешним миром»: DOM вне Angular, storage, аналитика.
- Не выводите данные через effect — для этого есть computed.
- Создавайте эффекты в контексте инжектора (конструктор/инициализатор), чтобы они корректно очищались.
Итоги. effect реагирует действием на изменение сигналов, авто-отслеживая зависимости и поддерживая очистку. Используйте его для побочных эффектов, а не для вывода значений. Дальше — управление состоянием на сигналах.
Закрепляем
Разложите по полочкам три инструмента реактивности. signal отвечает на вопрос «что хранить», computed — «что вывести из хранимого», а effect — «что сделать, когда что-то изменилось». Эффект — это наблюдатель: он выполняет побочное действие и автоматически перезапускается при изменении любого прочитанного им сигнала. Его территория — синхронизация с внешним миром: запись в localStorage, обновление заголовка вкладки, отправка аналитики, работа с DOM вне Angular.
Главная опасность эффектов — превратить их в свалку логики и создать бесконечные циклы. Если эффект читает сигнал и тут же его меняет, он будет перезапускать сам себя. Поэтому держите эффекты узкими и помните: если вы выводите значение — это работа для computed, а не для effect. Ещё одна важная деталь — очистка: если эффект заводит таймер или подписку, верните из него функцию очистки, чтобы Angular снял ресурс перед следующим запуском и при уничтожении компонента. Аккуратные эффекты незаметны; небрежные становятся источником утечек и мерцаний.
| Инструмент | Вопрос, на который отвечает |
|---|---|
| signal | Что хранить |
| computed | Что вывести |
| effect | Что сделать при изменении |