Перед вами перевод статьи Chidume Nnamdi, опубликованной на blog.bitsrc.io. Перевод публикуется с разрешения автора.



Появление библиотеки RxJS открыло массу новых возможностей в мире JS. Цель RxJS — достигать многого, используя небольшое количество кода. Прочитав эту статью, вы узнаете, как осуществлять обмен данными между компонентами приложения на React, применяя возможности RxJS.

Совет: используйте Bit для организации React-компонентов и обмена ими. Это позволит вашей команде быстрее разрабатывать свои приложения. Просто попробуйте.


React Components Collection

Redux


Обмен данными между несвязанными React-компонентами — это то, ради чего были созданы библиотеки управления состояниями. Существует множество шаблонов для управления состояниями, но наиболее известны два: Flux и Redux.

Redux популярен из-за своей простоты и использования чистых функций. Ведь благодаря им можно не сомневаться, что применение редьюсеров не приведет к каким-то побочным эффектам.

Работая с Redux, первым делом мы создаем централизованное хранилище данных:



Далее связываем компоненты с этим хранилищем и при желании обновляем или удаляем состояние. Любые изменения, внесенные в хранилище, будут отражены в компонентах, связанных с ним. Таким образом, поток данных распространяется на все компоненты, независимо от степени их вложенности. Компонент, находящийся на энном уровне иерархической структуры, способен передавать данные компоненту самого высокого уровня. Последний, в свою очередь, может передавать данные компоненту 21 уровня.

RxJS


С появлением RxJS использовать библиотеки управления состояниями стало гораздо проще. Многим понравился паттерн «Наблюдатель» (Observer), предоставляемый RxJS.

Мы просто создаем поток Observable и даем возможность всем компонентам прослушивать его. Если какой-то компонент добавляется к потоку, прослушивающие (или «подписанные») компоненты реагируют на обновление DOM.

Установка


Создаем приложение на React, используя create-react-app. Если у вас нет create-react-app, то сперва установите его глобально:

npm i create-react-app -g

Далее генерируем проект в React:

create-react-app react-prj

Переходим в директорию:

cd react-prj

Устанавливаем библиотеку rxjs:

npm i rxjs

У нас должен появиться файл, создающий новый экземпляр BehaviourSubject.

Почему мы используем BehaviorSubject?


BehaviorSubject — это один из Subject в библиотеке RxJS. Будучи дочерним компонентом Subject, BehaviorSubject позволяет множеству наблюдателей прослушивать поток, а также делает массовую рассылку событий этим наблюдателям. BehaviorSubject сохраняет последнее значение и передает его всем новым подписанным компонентам.

Таким образом, BehaviorSubject:

  • Позволяет осуществлять массовую рассылку.
  • Хранит последние значения, опубликованные подписчиками, и делает массовую рассылку этих значений.



В папке src находится файл messageService.js, экспортирующий подписчику экземпляр BehaviorSubject и объект messageService. Объект-подписчик создается в начале файла — так он доступен для любого импортирующего компонента. У объекта messageService имеется функция отправки, принимающая параметр msg: в нем содержатся данные, которые нужны для передачи всем прослушивающим компонентам. В теле функции мы вызываем метод emit. Он осуществляет массовую рассылку данных подписанным компонентам в объекте-подписчике.

Предположим, что у нас есть следующие компоненты:

  • ConsumerA;
  • ConsumerB;
  • ProducerA;
  • ProducerB.

В иерархической структуре они выглядят так:



Компонент приложения передает сообщение ProducerA и ConsumerB. ProducerA отправляет данные ConsumerA, а сообщение от ConsumerB попадает к ProducerB.



Компоненты ConsumerA и ConsumerB имеют индивидуальный счетчик состояния. В их методе componentDidMount они подписаны на один и тот же поток subscriber. Как только публикуется какое-либо событие, у обоих компонентов обновляется счетчик.

У ProducerA и ProducerB есть кнопки Increment Counter и Decrement Counter, которые при нажатии выдают 1 или -1. Подписанные компоненты ConsumerA и ConsumerB подхватывают событие и запускают свои функции обратного вызова, обновляя значение счетчика состояния и DOM.



Посмотрим на иерархическую структуру еще раз:



ProducerB передает данные ConsumerA, хотя они абсолютно не связаны. ProducerA передает данные ConsumerB, не являясь его родительским компонентом. В этом вся суть RxJS: мы просто создали центральный узел потока событий и позволили компонентам прослушивать его. Когда какой-либо компонент генерирует события, прослушивающие компоненты тут же подхватывают их.

Поиграть с приложением можно на stackblitz: https://react-lwzp6e.stackblitz.io

Заключение


Итак, мы увидели, как можно осуществлять обмен данными между React-компонентами, применяя RxJS. Мы использовали BehaviourSubject для создания централизованного потока данных, а затем позволили остальным компонентам подписаться на этот поток. Теперь, когда один из компонентов генерирует данные, прочие компоненты также получают их. Уровень компонентов в иерархической структуре неважен.

Если у вас есть вопросы относительно этой темы или вы хотите, чтобы я что-то добавил, исправил или удалил, напишите об этом в комментариях, в электронном письме или в личном сообщении.

Спасибо за внимание!

Комментарии (19)


  1. staticlab
    24.05.2019 13:28
    +1

    Отписываться от сообщений при удалении компонентов не нужно?


    1. psFitz
      24.05.2019 13:37

      Нужно


  1. JustDont
    24.05.2019 14:05

    В показанном разрезе неясно, зачем это вообще надо. Если вы всё равно в каждом условно-сложном компоненте руками работаете через реактовый собственный стейт, то нафига вообще тащить RxJS сюда? Если у вас глобальный объект-хранилище, к которому вы руками лезете из каждого места приложения чтоб подписаться или запустить действие — то, опять же, нафига тут весь RxJS? Тут хватит простейшей наколенной подписочной логики — вместо того, чтоб руками написать 1 коллекцию (подписок) и 1 цикл (прохода по коллекции и дерганья каждой подписки) предлагается подключить RxJS целиком? Серьезно?

    Я вообще нисколько не против, просто статья ну совершенно не демонстрирует ничего серьезного, что можно б было сделать через RxJS. То, что продемонстрировано — это полный детсад вида «подключим lodash потому, что я не знаю, что такое ...».


    1. serf
      24.05.2019 15:28
      -1

      Если у вас глобальный объект-хранилище, к которому вы руками лезете из каждого места приложения чтоб подписаться или запустить действие — то, опять же, нафига тут весь RxJS?
      RxJS нотификейшен будет триггером перересовки. Тут ведь можно использовать множество операторов, например distinctUntilChanged для предотвращения лишних отрисовок (игнорировать холостные для определенного компонента циклы изменения стора).


      1. JustDont
        24.05.2019 15:59

        Так вот об этом и надо писать. А не о том, что получили нотификейшен, дернули setState, и такие сидим радуемся о том, какая же у нас хорошая интеграция RxJS с реактом.


        1. serf
          24.05.2019 18:14

          Все с чего-то начинают.

          PS я только сейчас заметил что это перевод, это объясняет некотрые вещи, они там на западе любят постить в блоги поверхностную информацию лишь бы показать активность блога.


          1. JustDont
            24.05.2019 18:17
            +1

            Я категорически против того, чтоб в эпоху нынешнего веба с гуглопочтой на 6.7Мб люди «начинали» на этой ниве с подключения немелкой библиотеки примерно низачем.

            PS: Дело не в собирательном «западе», а в том, что это сейчас повсеместно. Вон на хабр посмотрите. У нас соотношение статей полезных к статьям из воды или в стиле КО — тоже не слишком-то хорошее.


            1. serf
              24.05.2019 18:19

              Добавил выше PS. Не факт что автор использует такой подход, на западе это норма писать поверхностные блог-статьи.


  1. mayorovp
    24.05.2019 14:55

    Я бы не рекомендовал использовать примеры кода из этой статьи как образец (хвала автору, он выложил их картинками чтобы максимально затруднить это).


    Замеченные мною ошибки:


    1. в первом файле явно предполагалось, что другие будут подписываться на subscriber, а публиковать значения через messageService. Остальные файлы используют только subscriber, а про messageService все забыли;


    2. странное наименование: вот кем надо быть, чтобы назвать источник данных словом subscriber?


    3. используемый BehaviorSubject предназначен для передачи значений, а не дельт между ними. Обратите внимание на его основное свойство: воспроизведение последнего полученного значения для новых подписчиков. Какой смысл это свойство имеет для дельты? Для передачи подобных сообщений нужно использовать простой Subject!


    4. а подписку отменять кто за вас будет?



    1. serf
      24.05.2019 15:23

      BehaviorSubject имеет смысл использовать как реактивный центральный стор. То есть это не просто передача данных но и фиксация последнего состояния. Но нужно использовать именно как единый/центральный стор, иначе получится каша.


      1. mayorovp
        24.05.2019 15:26

        Не обязательно единый центральный, там есть варианты. Но да, он именно что фиксирует состояние. А у автора состояние — своё локальное у для каждого подписчика, глобального состояния вообще нет. Потому и BehaviorSubject использовать нельзя.


  1. xState_level80
    24.05.2019 20:43

    Context API, hooks API, redux, mobx… и тут ещё RxJs для управления стейтом. Как начинающий фронтэндщик я уже давно в шоке!


    1. mayorovp
      24.05.2019 21:38
      +1

      Что значит "и тут ещё"? RxJs древнее реакта...


      1. xState_level80
        25.05.2019 16:24

        mayorovp Имелось в виду для использования в качестве глобального состояния


        1. mayorovp
          25.05.2019 17:08

          В качестве глобального состояния rxjs можно было использовать с самого начала


    1. serf
      25.05.2019 11:19
      -1

      Дело в том что React создавалась как библиотека продвинутый-шаблонизатор. Но народ начал использовать это изделие как составную часть самособранных-фреймворков. В итоге вылезло множество граблей которых нет во взрослых фреймворках, и в React соответственно начали добавлять множество костылей чем и до сих пор занимаются.


      1. JustDont
        25.05.2019 14:19

        в React соответственно начали добавлять множество костылей чем и до сих пор занимаются.

        Это какие костыли добавляют в реакт, расскажите?


        1. serf
          25.05.2019 15:10

          В чендж-логе можно посмотреть.


  1. Ni55aN
    27.05.2019 11:46

    В некоторых кейсах такой глобальный сервис не подойдет, почему бы сразу не сделать это через DI? Допустим, пробросить экземпляр в провайдер Context API, причем только на необходимом уровне иерархии. В итоге, приходим к том, что уже давно умеет Angular и Vue