Я вижу много вопросов по темам на Stack Overflow. Недавно я видел один, в котором спрашивали, как следует использовать AsyncSubject
. Этот вопрос побудил меня написать эту статью, чтобы показать, почему необходимы различные типы предметов и как они используются в самом RxJS.
Каковы варианты использования предметов?
В своей статье О предметах Бен Леш утверждает, что:
… [Многоадресная передача] - это основной вариант использования субъектов в RxJS.
Мы рассмотрим многоадресную рассылку более подробно позже в этой статье, но пока достаточно знать, что она включает в себя получение уведомлений от одного наблюдаемого источника и их пересылку одному или нескольким наблюдателям назначения.
Это соединение наблюдателей с наблюдаемым - вот что суть предмета. Они могут это делать, потому что сами субъекты одновременно являются наблюдателями и наблюдаемыми.
Как можно использовать предметы?
Давайте возьмем в качестве примера компонент Angular: awesome-component
. Наш компонент делает несколько замечательных вещей и имеет внутренний наблюдаемый объект, который генерирует значения, когда пользователь взаимодействует с компонентом.
Чтобы родительские компоненты могли подключаться к наблюдаемому, awesome-component
принимает свойство ввода observer
, которое подписывается на наблюдаемое. Это означает, что родитель может подключиться к наблюдаемому, указав наблюдателя, например:
Когда наблюдатель подключен, родитель подключен и получает значения от awesome-component
. Однако это по сути то же самое, как если бы awesome-component
испустил свои значения с помощью выходного события. Так почему бы не использовать событие?
У наблюдаемых есть то преимущество, что ими легко манипулировать. Например, легко добавить фильтрацию и устранение ошибок, просто применив несколько операторов. Но у родительского компонента есть наблюдатель, а не наблюдаемое, так как мы можем применять операторы?
Субъекты являются как наблюдателями, так и наблюдаемыми, поэтому, если мы создадим Subject
, его можно будет передать awesome-component
(как наблюдатель), и к нему может быть применено противодействие (как наблюдаемое), например:
Субъект связывает наблюдателя, выполняющего что-то со значением, с наблюдаемым awesome-component
, но с применением операторов, выбранных родительским компонентом.
Составление различных наблюдаемых
Используя Subject
для составления наблюдаемого, awesome-component
может использоваться разными компонентами по-разному. Например, другой компонент может интересоваться только последним переданным значением. Этот компонент может использовать оператор last
:
Интересно, что есть еще один способ, которым компонент может выбрать получение только последнего отправленного значения из awesome-component
: он может использовать другой тип объекта. AsyncSubject
испускает только последнее полученное значение, поэтому альтернативной реализацией будет:
Если использование AsyncSubject
эквивалентно составлению наблюдаемого с использованием оператора Subject
и last
, зачем усложнять RxJS классом AsyncSubject
?
Дело в том, что темы предназначены в первую очередь для многоадресной рассылки.
Здесь они эквивалентны, потому что есть один подписчик - наблюдатель, который делает что-то со значением. В ситуации многоадресной рассылки может быть несколько подписчиков, и применение оператора last
к Subject
не приведет к тому же поведению, что и AsyncSubject
, для поздних подписчиков.
Давайте подробнее рассмотрим многоадресную рассылку.
Как темы используются в RxJS?
Ядро инфраструктуры многоадресной рассылки RxJS реализуется с помощью одного оператора: multicast
. Оператор multicast
применяется к наблюдаемому источнику, берет предмет (или фабрику, создающую предмет) и возвращает наблюдаемое, составленное из предмета.
Оператор multicast
чем-то похож на awesome-component
в наших примерах: мы можем получить наблюдаемое, которое демонстрирует другое поведение, просто передав другой тип объекта.
Когда базовый Subject
передается в multicast
:
- подписчики многоадресного наблюдаемого получают уведомления
next,
error
иcomplete
источника; а также - поздние подписчики, то есть те, которые подписываются после того, как было отправлено уведомление
error
илиcomplete
, получают уведомлениеerror
илиcomplete
.
Важно отметить, что до тех пор, пока multicast
не будет передан фабрике, поздние подписчики не произведут новую подписку на источник.
Чтобы составить многоадресный наблюдаемый объект, который пересылает последнее отправленное next
уведомление исходного объекта наблюдения всем подписчикам, недостаточно применить оператор last
к многоадресному наблюдаемому объекту, который был создан с использованием Subject
. Поздние подписчики на такое наблюдаемое не получат последнее отправленное next
уведомление; они получат только complete
уведомление.
Чтобы опоздавшие подписчики получили последнее отправленное next
уведомление, оно должно быть сохранено в состоянии субъекта. Это то, что делает AsyncSubject
, и поэтому класс AsyncSubject
необходим.
А как насчет других предметных классов?
Есть еще два варианта темы: BehaviorSubject
и ReplaySubject
.
Чтобы понять BehaviorSubject
, давайте взглянем на другой компонентный пример:
Здесь родительский компонент подключается к awesome-component
с помощью Subject
и применяет оператор startWith
. Использование startWith
гарантирует, что родитель получит значение "awesome"
при подписке, за которым следуют значения, выдаваемые awesome-component
- всякий раз, когда они будут выданы.
Точно так же, как AsyncSubject
заменил использование оператора Subject
и last
, BehaviorSubject
может заменить использование оператора Subject
и startWith
- конструктор BehaviorSubject
принимает значение, которое в противном случае было бы передано startWith
.
Однако использование операторов Subject
и startWith
не повлияет на желаемое поведение в ситуации с несколькими абонентами. Первый подписчик увидит ожидаемое поведение, но последующие подписчики всегда будут получать значение startWith
, даже если источник уже отправил значение.
Если используется BehaviorSubject
, последующие подписчики получат начальное значение, если источник еще не отправил, или последнее отправленное значение, если оно было. Это возможно, потому что BehaviorSubject
сохраняет значение в своем состоянии.
Для ReplaySubject
нет аналогии с одним подписчиком, поскольку концепция воспроизведения уже полученных уведомлений по своей сути является многопользовательской. Чтобы облегчить воспроизведение уведомлений для последующих подписчиков, ReplaySubject
сохраняет уведомления в своем состоянии.
Так как же использовать эти предметы?
Теперь, когда мы увидели, что делают различные предметы и почему они необходимы, как их следует использовать? Что ж, вполне вероятно, что единственными предметными классами, которые вам когда-либо понадобятся, будут Subject
и BehaviorSubject
.
Subject
отлично подходит для подключения наблюдателя к наблюдаемому, а BehaviorSubject
хорошо подходит для представления атома состояния. А для ситуаций с многоадресной рассылкой есть альтернативы использованию темы.
RxJS содержит операторы многоадресной рассылки, которые используют различные предметные классы, и точно так же, как я предпочитаю использование наблюдаемых создателей RxJS (например, fromEvent
), а не вызовов Observable.create
, для ситуаций многоадресной рассылки я предпочитаю использовать операторы RxJS, а не явные темы:
publish
илиshare
можно использовать вместоSubject
;publishBehavior
можно использовать вместоBehaviorSubject
;publishLast
можно использовать вместоAsyncSubject
; а такжеpublishReplay
илиshareReplay
можно использовать вместоReplaySubject
.
Более подробно операторы publish
и share
описаны в моих статьях:
- RxJS: Общие сведения об операторах публикации и совместного использования; а также
- RxJS: Как использовать refCount.
Этот пост также опубликован в моем личном блоге: ncjamieson.com.