Angular Signals¶
Angular Signals — это система, которая детально отслеживает, как и где ваше состояние используется в приложении, позволяя фреймворку оптимизировать обновления рендеринга.
Сигналы Angular доступны для предварительного просмотра разработчиком. Они готовы к тому, чтобы вы попробовали их, но могут измениться до того, как они станут стабильными.
Что такое сигналы?¶
Сигнал — это обертка вокруг значения, которая может уведомлять заинтересованных потребителей об изменении этого значения. Сигналы могут содержать любое значение, от простых примитивов до сложных структур данных.
Значение сигнала всегда считывается через функцию getter, что позволяет Angular отслеживать, где используется сигнал.
Сигналы могут быть либо записываемыми, либо только для чтения.
Записываемые сигналы¶
Записываемые сигналы предоставляют API для обновления их значений напрямую. Вы создаете записываемые сигналы, вызывая функцию signal
с начальным значением сигнала:
1 2 3 |
|
Чтобы изменить значение записываемого сигнала, вы можете либо .set()
его непосредственно:
1 |
|
или использовать операцию .update()
для вычисления нового значения на основе предыдущего:
1 2 |
|
При работе с сигналами, содержащими объекты, иногда полезно мутировать объект напрямую. Например, если объект представляет собой массив, вы можете захотеть вставить новое значение, не заменяя массив полностью. Для такого внутреннего изменения используйте метод .mutate
:
1 2 3 4 5 6 7 |
|
Записываемые сигналы имеют тип WritableSignal
.
Вычисляемые сигналы¶
Сигнал вычисляемый получает свое значение от других сигналов. Определите его, используя computed
и указав функцию вычисления:
1 2 3 4 |
|
Сигнал doubleCount
зависит от count
. Когда count
обновляется, Angular знает, что все, что зависит от count
или doubleCount
, также должно обновиться.
Вычисления как лениво оцениваются, так и мемоизируются¶
Функция вычисления doubleCount
не запускается для вычисления своего значения до тех пор, пока doubleCount
не будет прочитан в первый раз. После вычисления это значение кэшируется, и последующие чтения doubleCount
будут возвращать кэшированное значение без пересчета.
Когда count
изменяется, он сообщает doubleCount
, что его кэшированное значение больше не действительно, и значение пересчитывается только при следующем чтении doubleCount
.
В результате, в вычисляемых сигналах можно безопасно выполнять вычислительно дорогие производные, такие как фильтрация массивов.
Вычислимые сигналы не являются сигналами, доступными для записи¶
Вы не можете напрямую присваивать значения вычисляемому сигналу. То есть,
1 |
|
выдает ошибку компиляции, поскольку doubleCount
не является WritableSignal
.
Вычисленные зависимости сигналов являются динамическими¶
Отслеживаются только те сигналы, которые действительно считываются во время вычисления. Например, в этом вычислении сигнал count
читается только условно:
1 2 3 4 5 6 7 8 9 |
|
При чтении conditionalCount
, если showCount
равно false
, сообщение "Nothing to see here!" возвращается без чтения сигнала count
. Это означает, что обновление count
не приведет к повторному вычислению.
Если позже showCount
будет установлен в true
и conditionalCount
будет прочитан снова, деривация будет выполнена заново и возьмет ветвь, где showCount
будет true
, возвращая сообщение, которое показывает значение count
. Изменения в count
затем аннулируют кэшированное значение conditionalCount
.
Обратите внимание, что зависимости могут быть как удалены, так и добавлены. Если позже showCount
снова будет установлен в false
, то count
больше не будет считаться зависимостью conditionalCount
.
Чтение сигналов в компонентах OnPush
¶
Когда компонент OnPush
использует значение сигнала в своем шаблоне, Angular будет отслеживать сигнал как зависимость этого компонента. Когда сигнал обновляется, Angular автоматически помечает компонент, чтобы обеспечить его обновление при следующем запуске обнаружения изменений. Дополнительную информацию о компонентах OnPush
см. в руководстве Skipping component subtrees.
Эффекты¶
Сигналы полезны тем, что они могут уведомлять заинтересованных потребителей об изменениях. Эффект — это операция, которая выполняется всякий раз, когда одно или несколько значений сигнала изменяются. Вы можете создать эффект с помощью функции effect
:
1 2 3 |
|
Эффекты всегда запускаются по крайней мере один раз. Когда эффект запускается, он отслеживает все считанные значения сигнала. Когда любое из этих значений сигнала изменяется, эффект запускается снова. Подобно вычисляемым сигналам, эффекты отслеживают свои зависимости динамически и отслеживают только те сигналы, которые были прочитаны при последнем выполнении.
Эффекты всегда выполняются асинхронно, во время процесса обнаружения изменений.
Применение эффектов¶
Эффекты редко нужны в большинстве прикладных программ, но могут быть полезны в определенных обстоятельствах. Вот несколько примеров ситуаций, в которых эффект
может быть хорошим решением:
-
Протоколирование отображаемых данных и их изменения, либо для аналитики, либо в качестве инструмента отладки.
-
синхронизация данных с
window.localStorage
. -
Добавление пользовательского поведения DOM, которое не может быть выражено с помощью синтаксиса шаблонов.
-
Выполнение пользовательского рендеринга в
<canvas>
, библиотеку диаграмм или другую стороннюю библиотеку пользовательского интерфейса.
Когда не следует использовать эффекты¶
Избегайте использования эффектов для распространения изменений состояния. Это может привести к ошибкам ExpressionChangedAfterItHasBeenChecked
, бесконечным циклическим обновлениям или ненужным циклам обнаружения изменений.
Из-за этих рисков установка сигналов в эффектах запрещена по умолчанию, но может быть разрешена в случае крайней необходимости.
Контекст инъекции¶
По умолчанию для регистрации нового эффекта с помощью функции effect()
требуется "контекст инъекции" (доступ к функции inject
). Самый простой способ обеспечить это — вызвать effect
в компоненте, директиве или конструкторе
сервиса:
1 2 3 4 5 6 7 8 9 10 |
|
В качестве альтернативы эффект может быть назначен полю (что также дает ему описательное имя).
1 2 3 4 5 6 7 8 |
|
Чтобы создать эффект вне конструктора, вы можете передать Injector
в effect
через его опции:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Уничтожение эффектов¶
Когда вы создаете эффект, он автоматически уничтожается, когда уничтожается его объемлющий контекст. Это означает, что эффекты, созданные внутри компонентов, уничтожаются при уничтожении компонента. То же самое относится к эффектам внутри директив, сервисов и т.д.
Эффекты возвращают EffectRef
, который можно использовать для их уничтожения вручную, с помощью операции .destroy()
. Это также можно совместить с опцией manualCleanup
, чтобы создать эффект, который будет действовать до тех пор, пока его не уничтожат вручную. Будьте осторожны, чтобы действительно очистить такие эффекты, когда они больше не нужны.
Расширенные темы¶
Функции равенства сигналов¶
При создании сигнала вы можете опционально указать функцию равенства, которая будет использоваться для проверки того, действительно ли новое значение отличается от предыдущего.
1 2 3 4 5 6 7 |
|
Функции равенства могут быть предоставлены как для записываемых, так и для вычисляемых сигналов.
Для записываемых сигналов функция .mutate()
не проверяет равенство, поскольку она изменяет текущее значение без создания новой ссылки.
Чтение без отслеживания зависимостей¶
В редких случаях вы можете захотеть выполнить код, который может читать сигналы в реактивной функции, такой как computed
или effect
, без создания зависимости.
Например, предположим, что когда currentUser
меняется, значение counter
должно быть зарегистрировано. Создайте effect
, который считывает оба сигнала:
1 2 3 |
|
Этот пример регистрирует сообщение, когда изменяется либо currentUser
, либо counter
. Однако, если эффект должен выполняться только при изменении currentUser
, то чтение counter
является случайным и изменения counter
не должны регистрировать новое сообщение.
Вы можете предотвратить отслеживание чтения сигнала, вызвав его геттер с untracked
:
1 2 3 |
|
untracked
также полезен, когда эффект должен вызвать внешний код, который не должен рассматриваться как зависимость:
1 2 3 4 5 6 7 8 |
|
Функции очистки эффектов¶
Эффекты могут запускать длительные операции, которые должны быть отменены, если эффект уничтожен или запущен снова до завершения первой операции. Когда вы создаете эффект, ваша функция может опционально принимать функцию onCleanup
в качестве первого параметра. Эта функция onCleanup
позволяет вам зарегистрировать обратный вызов, который будет вызван перед началом следующего запуска эффекта или когда эффект будет уничтожен.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|