Замените ручные пересчёты на signal, computed и effect: производные значения пересчитываются сами.
В этом уроке мы разберём четыре понятия. Чтобы не путаться, сразу дадим им «человеческие» имена:
signal() — как завести переменную, которая сама сообщает об изменениях.computed() — как итоговый счёт, который считается сам.effect() — как будильник, срабатывающий при смене данных.update()/set() — как делать новый снимок данных, а не менять старый.Список задач — сигнал. Количество активных и выполненных — computed.
Контекст. В проекте TaskFlow (список задач) мы храним все задачи в одном сигнале. А счётчики «активно» и «готово» хотим видеть без ручного подсчёта — пусть они считаются сами.
💡 Аналогия из жизни. Сигнал tasks — это как группа в мессенджере: изменил сообщение — и всем подписчикам приходит уведомление. А computed — это итоговый счёт: производное значение, которое считается само, как итог в чеке, когда ты добавляешь товары.
import { signal, computed } from '@angular/core';
// Сигнал tasks — наш список задач (доска объявлений).
// Тип Task[] значит: «массив задач».
const tasks = signal<Task[]>([
{ id: 1, title: "Изучить сигналы", completed: false },
{ id: 2, title: "Написать computed", completed: true }
]);
// computed — производное значение. Оно смотрит на tasks()
// и считает, сколько задач ещё НЕ выполнено (completed === false).
const activeCount = computed(() =>
tasks().filter(t => !t.completed).length
);
// Аналогично — сколько задач УЖЕ выполнено.
const doneCount = computed(() =>
tasks().filter(t => t.completed).length
);
// При любом изменении tasks() счётчики пересчитаются сами.
// update() берёт старый список и возвращает НОВЫЙ (неизменяемо).
tasks.update(list =>
[...list, { id: 3, title: "effect()", completed: false }]
);
Пошаговый разбор:
tasks с двумя задачами. Пока активна 1, выполнена 1.activeCount и doneCount — они «подписаны» на tasks.tasks.update(...): берём старый список, добавляем третью задачу через [...list, ...] (спред — копия + новый элемент).update значение activeCount() станет 2 без ручного пересчёта — вы просто читаете activeCount(), а число уже правильное.Видимый список зависит от выбранного фильтра.
Контекст. В TaskFlow есть кнопки «Все / Активные / Готовые». Вместо того чтобы вручную перестраивать список при каждом нажатии, мы делаем видимый список производным от фильтра.
💡 Аналогия из жизни. Фильтрация .filter() — как ситечко для чая: включаешь фильтр «готовые» — и ситечко пропускает только выполненные задачи, остальное остаётся за бортом. А сам computed — это итоговый счёт: производное значение, которое считается само.
// Сигнал filter хранит текущий выбор: 'all' | 'active' | 'done'.
// done здесь — ТОЛЬКО значение фильтра (какие задачи показывать),
// а не поле задачи «выполнена». В TaskFlow поле задачи называется completed.
const filter = signal<'all' | 'active' | 'done'>('all');
// visibleTasks — что именно показываем на экране.
// Зависит и от tasks(), и от filter().
const visibleTasks = computed(() => {
const f = filter(); // читаем текущий фильтр
if (f === 'done') return tasks().filter(t => t.completed); // только готовые
if (f === 'active') return tasks().filter(t => !t.completed); // только активные
return tasks(); // 'all' — показываем всё
});
Пошаговый разбор:
filter со значением 'all' по умолчанию.computed читаем filter() — этим мы «подписываемся» на него.filter.set('done')), visibleTasks пересчитается сам.completed (выполнена ли). А 'done' — это просто одно из значений фильтра, выбранное пользователем. Не путайте их: completed отвечает на вопрос «задача готова?», а filter()==='done' — на вопрос «что сейчас показываем?».Реагируйте на изменения, не возвращая значение (лог, синхронизация).
Контекст. Иногда при изменении данных нужно сделать что-то «сбоку»: записать в лог, сохранить в браузере, обновить заголовок. Для этого — effect(), а не computed() (который возвращает значение).
💡 Аналогия из жизни. effect — как будильник. Ты не спрашиваешь у будильника «сколько времени» (он ничего не возвращает), ты просто говоришь: «когда время изменится — разбуди меня». Так же effect: «когда tasks меняется — сделай это».
effect(() => {
// Этот код запускается каждый раз, когда меняются
// tasks() или activeCount() (мы их «читаем» внутри).
console.log('Сейчас задач:', tasks().length,
'активных:', activeCount());
});
Пошаговый разбор:
effect с функцией-действием.tasks() и activeCount() — значит, effect «подписан» на них.effect() запускается только внутри контекста инъекции (в компоненте или сервисе), иначе Angular выбросит ошибку. Простыми словами: effect должен «жить» внутри Angular-компонента, а не в случайном скрипте.Симуляция сигналов на чистом JS: обновляем состояние — и производные пересчитываются.
Контекст. Ниже — упрощённая модель сигналов без Angular, чтобы вы «пощупали» идею: есть значение, есть подписчики, и при изменении значения запускается перерисовка.
💡 Аналогия из жизни. Это как группа в мессенджере: у сигнала есть список подписчиков (subs). Меняешь значение (set) — и всем подписчикам приходит уведомление, один из которых перерисовывает экран (render).
Что увидит студент. Сначала на экране: «Всего: 2, активных: 1». Через секунду текст сам сменится на «Всего: 3, активных: 2» — никто не вызывал render() вручную, это сделал сигнал.
computed отличается от обычной функции? (Подсказка: обычную функцию надо вызвать заново и она не знает, когда данные изменились.)[...list, ...])? (Подсказка: чтобы сигнал «заметил» изменение и оповестил подписчиков.)effect()? (Подсказка: внутри компонента или сервиса Angular.)Практика к уроку 5. Далее: Практика к уроку 6 →
| Термин | Жизненная аналогия | Коротко |
|---|---|---|
signal | Группа в мессенджере: изменил сообщение — и всем пришло уведомление. | Переменная с подписчиками, которые узнают об изменении. |
computed | Итоговый счёт в чеке: меняешь товары — сумма считается сама. | Производное значение, пересчитывается автоматически. |
effect | Будильник: не отвечает, а просто будит при смене времени. | Действие «сбоку» при изменении данных, ничего не возвращает. |
| Состояние (state) | Статус заказа: «в обработке / готов / ошибка». | Текущее положение дел в приложении. |
| Неизменяемость | Новый снимок вместо правки оригинала: новый кадр, а не рисунок поверх. | Создаём новый объект, а старый не трогаем. |
filter | Ситечко для чая: пропускает только нужное, остальное — за бортом. | Оставляет в списке лишь элементы по условию. |
map | Конвейер: каждая деталь проходит обработку и выходит преображённой. | Превращает каждый элемент списка по правилу. |
1. Сигнал сам оповещает подписчиков при изменении значения. Верно или неверно?
Ответ: Верно. Как в группе мессенджера: поменял сообщение — и всем пришло уведомление, пересчитывать вручную не нужно.
2. computed нужно вызывать заново каждый раз, чтобы получить свежее значение. Верно или неверно?
Ответ: Неверно. computed — как итоговый счёт: он пересчитывается сам при изменении исходных данных, достаточно просто его прочитать.
3. Чтобы сигнал «заметил» изменение, список задач лучше менять неизменяемо (через spread). Верно или неверно?
Ответ: Верно. Как новый снимок вместо правки оригинала: создаём новый список, и сигнал видит, что данные сменились, и оповещает подписчиков.