Обмен данными между React-компонентами с использованием библиотеки RxJS

Автор оригинала: Chidume Nnamdi
  • Перевод
Перед вами перевод статьи 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 для создания централизованного потока данных, а затем позволили остальным компонентам подписаться на этот поток. Теперь, когда один из компонентов генерирует данные, прочие компоненты также получают их. Уровень компонентов в иерархической структуре неважен.

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

Спасибо за внимание!
Plarium
134,74
Разработчик мобильных и браузерных игр
Поделиться публикацией

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

    +4

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

      +2

      Нужно

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

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

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

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

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


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


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


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


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


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


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

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

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

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

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

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

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

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

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое