Сигналы Angular приносят множество преимуществ, одним из которых является их способность легко интегрироваться с шаблонами и «автоматизировать» обнаружение изменений. Эта автоматизация означает, что компонент, настроенный со стратегией обнаружения изменений OnPush
, будет перепроверен во время следующего цикла обнаружения изменений, что избавляет разработчиков от необходимости вручную внедрять ChangeDetectorRef
и вызывать markForCheck
. Чтобы проиллюстрировать это преимущество, рассмотрим следующий пример:
@Component({ selector: 'app-foo', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `{{ num }}`, }) export class FooComponent { num = 1; private cdr = inject(ChangeDetectorRef); ngOnInit() { setTimeout(() => { this.num = 2; this.cdr.markForCheck(); }, 3000); } }
Здесь мы явно вводим ChangeDetectorRef
и вызываем markForCheck
после обновления состояния компонента. Новый способ работы с сигналами:
@Component({ selector: 'app-foo', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `{{ num() }}`, }) export class FooComponent { num = signal(0) ngOnInit() { setTimeout(() => this.num.set(1), 3000); } }
В этой улучшенной версии мы используем signal
, и, что примечательно, представление по-прежнему обновляется без необходимости вручную вызывать markForCheck
. В этой статье мы углубимся во внутреннюю работу этой функции, проливая свет на то, как сигналы упрощают обнаружение изменений в приложениях Angular.
На более высоком уровне абстракции вы можете представить текущий шаблон как consumer
, а каждый сигнал - как producer
. Когда сигнал подвергается изменению значения, он немедленно уведомляет шаблон, который впоследствии инициирует вызов markForCheck
.
Angular использует класс ReactiveLViewConsumer
, расширение класса ReactiveNode
, для упрощения реактивной архитектуры. В этой архитектуре каждый реактивный узел соответствует узлу в реактивном графе. Эти узлы могут играть различные роли, выступая в качестве производителей реактивных значений, потребителей других реактивных значений или выполняя обе роли одновременно.
В нашем конкретном контексте ReactiveLViewConsumer
служит потребителем для signals
, который мы используем в наших шаблонах.
Когда Angular выполняет функцию компонента template
, его начальный шаг включает получение ссылки на потребителя реактивного представления:
export function executeTemplate<T>( tView: TView, lView: LView<T>, templateFn: ComponentTemplate<T>, rf: RenderFlags, context: T) { const consumer = getReactiveLViewConsumer(lView, REACTIVE_TEMPLATE_CONSUMER); ... }
Функция getReactiveLViewConsumer
просто извлекает текущий экземпляр потребителя реактивного представления из назначенного слота в LView
или создает новый экземпляр, если он не существует.
Затем он переходит к вызову функции runInContext
внутри класса ReactiveLViewConsumer
. Эта функция отвечает за организацию следующих задач:
class ReactiveLViewConsumer { runInContext( fn: HostBindingsFunction<unknown> | ComponentTemplate<unknown>, rf: RenderFlags, ctx: unknown, ): void { const prevConsumer = setActiveConsumer(this); this.trackingVersion++; try { fn(rf, ctx); } finally { setActiveConsumer(prevConsumer); } } }
Он временно устанавливает в качестве текущего потребителя текущий экземпляр ReactiveLViewConsumer
, гарантируя, что все реактивные значения, к которым осуществляется доступ во время функции выполнения шаблона, присваиваются этому потребителю.
Проще говоря, это означает, что наш потребитель реактивного представления фактически отметил и зарегистрировал каждый signal
, используемый в шаблоне. Это происходит, когда мы обращаемся к signal
, как вы можете видеть здесь:
class WritableSignalImpl<T> extends ReactiveNode { // ... signal(): T { this.producerAccessed(); return this.value; } }
Вызывается метод producerAccessed
, помечающий текущий сигнал как зависимость производителя для нашего потребителя реактивного представления.
Двигаясь дальше, когда signal
подвергается обновлению значения, он вызывает метод producerMayHaveChanged
, который впоследствии запускает метод onConsumerDependencyMayHaveChanged
для каждого потребителя, зарегистрированного для этого производителя.
В нашем конкретном контексте класс ReactiveLViewConsumer
переопределяет этот метод и при вызове, в свою очередь, инициирует операцию markViewDirty
для текущего представления.
Наконец, процесс завершается вызовом функции commitLViewConsumerIfHasProducers
. Эта функция служит для сохранения текущего экземпляра потребителя реактивного представления, но только если в шаблоне есть производители.
Следуйте за мной в Medium или Twitter, чтобы узнать больше об Angular и JS!