React observer что это
Intersection Observer API: примеры использования
Доброго времени суток, друзья!
Обзор
Intersection Observer API (IOA) позволяет приложению асинхронно наблюдать за пересечением элемента (target) с его родителем (root) или областью просмотра (viewport). Другими словами, этот API обеспечивает вызов определенной функции каждый раз при пересечении целевого элемента с root или viewport.
Вызов callback возвращает объект, содержащий записи об изменениях, произошедших с целевым элементом:
В сети полно информации по теории, но довольно мало материалов по практике использования IOA. Я решил немного восполнить этот пробел.
Примеры
«Ленивая» (отложенная) загрузка изображений
Задача: загружать (показывать) изображения по мере прокрутки страницы пользователем.
Результат:
Фон контейнера, выходящего за пределы области просмотра, белый.
При пересечении с областью просмотра наполовину, фон меняется на небесно-голубой.
Замена изображения
Задача: менять изображение-заполнитель на оригинальное при прокрутке страницы пользователем.
Результат:
Первое изображение загружено, поскольку находится в области просмотра. Второе — заполнитель.
При дальнейшей прокрутке заполнитель заменяется исходным изображением.
Изменение фона контейнера
Задача: менять фон контейнера при прокрутке страницы пользователем туда и обратно.
Результат:
Фон контейнера меняется от светло-синего…
Работа с видео
Задача: ставить запущенное видео на паузу и запускать его снова в зависимости от попадания видео в область просмотра.
Пока видео находится в области просмотра, оно проигрывается.
Как только видео выходит за пределы области просмотра больше чем на 40%, его воспроизведение приостанавливается. При попадании в область просмотра > 40% видео, его воспроизведение возобновляется.
Прогресс просмотра страницы
Задача: показывать прогресс просмотра страницы по мере прокрутки страницы пользователем.
Страница только что загрузилась, поэтому мы еще не просмотрели ни одного контейнера.
При достижении конца страницы в параграф выводится информация о просмотре 4 div.
Бесконечная прокрутка
Задача: реализовать бесконечный список.
Имеем 12 элементов списка. Последний элемент выходит за пределы области просмотра.
При попытке добраться до последнего элемента создается новый (последний) элемент, скрытый от пользователя. И так до бесконечности.
Изменение размеров дочернего элемента при изменении размеров родительского элемента
Задача: установить зависимость размеров одного элемента от другого.
При уменьшении ширины родительского элемента, уменьшается ширина дочернего элемента. При этом расстояние между ними почти всегда равняется 50px («почти» обусловлено реализацией обратного механизма).
Работа с анимацией
Задача: анимировать объект при его видимости.
Мы видим часть головы Барта. Барт прижался к левой стороне области просмотра.
При попадании более 50% Барта в область просмотра, он перемещается на середину. При выходе более 50% Барта за пределы области просмотра, он возвращается в начальное положение.
Изучаем и реализуем алгоритм работы правильного observer паттерна для react компонентов
Итак продолжаем развивать observer-паттерн. В предыдущей статье от старого и очень простого паттерна «observer» маленькими шагами мы пришли к mobx и написали его мини-версию. В этой статье мы напишем полноценную версию mobx которая реализует алгоритм обновления зависимостей в правильном порядке для избежания ненужных вычислений. Надо сказать что попытки описать этот алгоритм на хабре предпринимались и раньше в статьях товарища vintage про атомы тут, тут, и тут но там не описан в полной мере последний «правильный» порядок обновления о чем и будет речь в этой статье.
Итак в прошлой статье для того чтобы компоненты реакта автоматически подписывались на данные которые они рендерят и при изменении вызывался перерендер только нужных компонентов мы пришли к такой модификации observer паттерна
Давайте немного отрефакторим — вынесем логику установки глобального массива внутрь самого обзервера. Это можно представить как например ячейки таблицы в гугл-докс — есть ячейка которая просто хранит значение а есть ячейка которая хранит не только значение (которое будет закешировано) а и формулу(функцию) для ее пересчета. И заодно кроме формулы функции-пересчета мы добавим еще параметр функции для выполнения сайд-эффектов, как например вызов setState(<>) на компоненте, когда у нас изменится значение. В итоге получим вот такой вот класс Cell
Лишние вычисления вообще являются ключевым моментом при сравнении библиотек основанных на модели «ячеек и формул в таблице». Лишние вычисления могут появляться при неправильном (как у нашего примера выше) алгоритме определения какие зависимости нужно вызвать после того как изменилось значение в случае ромбовидной схеме зависимостей (когда в графе зависимостей присутствуют циклы)
Как мы это можем сделать? Если подумать то есть парочка вариантов.
Одним из вариантов является способ «dirty-clean» который описан автором mobx в его докладе про устройство mobx (https://www.youtube.com/watch?v=TfxfRkNCnmk) (забавно что автор по факту солгал потому в самом mobx реализуется не этот а более правильный алгоритм но об этом позже).
Таким образом мы получили вычисление label только один раз после того как все зависимые ячейки сами обновятся и будут иметь актуальное значение.
Другим вариантом (который по факту является оптимизированной версией первого) будет идея вызвать подписчиков в порядке увеличения их глубины. Под глубиной ячейки примем максимальное значение глубины своих зависимых ячеек + 1 а ячейка без формулы которая не имеет зависимостей будет иметь глубину 0. Получаем что firstName и lastName будут иметь значение 0, fullName будет иметь значение 1 а label будет иметь значение 2 потому что максимальное значение у подписчиков ( fullName и firstName ) равно 1, делаем +1 получаем 2.
Значение же глубины тоже нужно обновлять каждый раз когда добавляется новый подписчик сравнивая его значение с текущим значением ячейки.
Таким образом мы получим вызов подписчиков в правильном порядке и избежим лишний вычислений. Почти.
Итак существует тот самый «правильный» алгоритм, который ни при каких условиях и хитросплетенных зависимостях не вызовет двойного вычисления ячейки. Для начала приведу код, который по совместительству является почти полноценной версией mobx (за исключением массива и декораторов) всего в 85 строчках кода
А теперь описание:
Пусть ячейка будет иметь три состояния — «actual» (которое значит что значение формулы актуально), «dirty» (которое будет значит что как только вызовется get() ячейка должна пересчитаться) и «check». Теперь как только ячейка изменит свое значение она не будет сразу вызывать вычисление подписчиков в каком-либо порядке а пометит своих подписчиков как «dirty». А те в свою очередь тоже пометят своих подписчиков но только значением «check» а те в свою очередь тоже пометят своих подписчиков значением «check», и так далее рекурсивно до конца. То есть только подписчики той ячеки которая изменилась будут иметь значение «dirty» а все остальные до конца дерева — значение «check», а чтобы при рекурсивном вызове мы не зациклились надо вызывать рекурсию только для тех ячеек которые еще не были помечены (имеют значение «actual»).
В итоге, когда ячейка изменила свое значение и вызвала этот рекурсивный процесс для своих подписчиков, у нас в глобальном массиве PendingCells будут находится некие root-ячейки у которых нет зависимостей но которые прямо или косвенно могут зависеть и соотвественно либо будут пересчитываться (если вдруг все промежуточные ячейки в цепочке поменяют свое значение) либо не будут (если кто-то в этой цепочке при перевычислении не изменит свое значение)
Итак алгоритм на первый взгляд может показаться сложным но он достаточно простой — когда ячейка меняет свое значение у нас всего 2 этапа — первый этап это рекурсивный спуск чтобы пометить как dirty (для первого уровня) и check для всех остальных а второй этап это рекурсивный подъем при котором происходит актуализация значений.
Теперь проясню некоторые неочевидные моменты.
Второе — почему такое странное действие — внутри метода actualize() — проверить — если значение равно «check» то вызвать actualize() у зависимых ячеек и в процессе этого и также после цикла снова повторно проверить значение и если «dirty» то прервать цикл и вызвать перерасчет а если «check» то сбросить после цикла на «actual» и ничего не делать? Все дело в том что в процессе вызова actualize() у зависимых ячеек некоторые из них могут иметь значение «dirty» и как мы знаем они должны выполнить перерасчет. А при вычислении есть условие — если ячейка поменяла свое значение то она должна пометить своих слушателей как «dirty». И таким образом ячейка которая до этого была «check» может после актуализации своих зависимых ячеек сама изменить значение когда изменится кто-либо из них и соотвественно нужно проверить условие снова. Но только в этом случае если никакие зависимые ячейки не изменили свое значение то значит и самой ячейки смысла вычисляться нет и мы меняем значение с «check» на «actual»
Применение паттерна observer в Redux и Mobx
Паттерн «observer» известен наверное с момента появления самого ооп. Упрощенно можно представить что есть объект который хранит список слушателей и имеет метод «добавить», «удалить» и «оповестить», а внешний код либо подписывается либо оповещает подписчиков
Таким образом, если мы найдем легкий способ создавать такие «сторы» и подписываться на них, то mapStateToProps() будет не нужен, потому что эта зависимость от разных частей состояния уже выражается в существовании разных сторов.
Вместе с этим требование иммутабельности стора нужно трактовать немного по-другому — если мы в каждом отдельном сторе будем хранить только примитивные значение, то с точки зрения redux нет ничего зазорного в том чтобы вызвать user.firstName.set(«NewName») — поскольку строка это иммутабельное значение — то здесь происходит просто установка нового иммутабельного значения стора, точно так же как и в redux. В случаях когда нам нужно сохранить в «мини-сторе» объект или сложные структуры то можно просто вынести их в отдельные «мини-сторы». Например вместо этого
лучше написать так чтобы компоненты могли по отдельности зависеть то от «email» то от «address» и чтобы не было лишних «перерендеров»
Но эта проблема решается через геттеры и сеттеры javascript-а
А если вы не относитесь негативно к декораторам то этот пример можно еще больше упростить
В общем можно пока подвести итоги и сказать что 1) никакой магии в этом моменте нет — декораторы это всего лишь геттеры и сеттеры 2) геттеры и сеттеры всего лишь считывают и устанавливают root-state в «мини-сторе» а-ля redux
Идем дальше — для того чтобы подключить все это к реакту нужно будет в компоненте подписаться на поля которые в нем выводятся и потом отписаться в componentWillUnmount
Здесь функция connect оборачивает компонент или stateless-component (функцию) реакта и возвращает компонент который благодаря этому механизму автоподписки подписывается на нужные «мини-сторы».
В итоге у нас получился такой вот механизм автоподписок только на нужные данные и оповещений только когда эти данные изменились. Компонент будет обновляться только тогда когда изменились только те «мини-сторы» на которые он подписан. Учитывая, что в реальном приложении, где может быть тысячи этих «мини-сторов», с данным механизмом множественных сторов при изменении одного поля будут обновляться только те компоненты которые находятся в массиве подписчиков на это поле, а вот подходом redux когда мы подписываем все эти тысячи компонентов на один единственный стор, при каждом изменении нужно оповещать в цикле все эти тысячи компонентов (и при этом заставляя программиста вручную описывать от каких частей состояния зависят компоненты внутри mapStateToProps )
Более того этот механизм автоподписок способен улучшить не только redux а и такой паттерн как мемоизацию функций, и заменить библиотеку reselect — вместо того чтобы явно указывать в createSelector() от каких данных зависит наша функция, зависимости будут определяться автоматически точно так же выше сделано с функцией render()
Вывод
Mobx — управление состоянием вашего приложения
MobX это простое, опробованное в бою решение для управления состоянием вашего приложения. Этот туториал научит вас основным концептам MobX. MobX это автономная библиотека, но большинство используют ее в связке с React и этот туториал будет сфокусирован на этой комбинации.
Основная идея
Состояние (state ориг.) это сердце каждого приложения и нет более быстрого способа создания забагованого, неуправляемого приложения, как отсутствие консистентности состояния. Или состояние, которое несогласованно с локальными переменными вокруг. Поэтому множество решений по управлению состоянием пытаются ограничить способы, которыми можно его изменять, например сделать состояние неизменяемым. Но это порождает новые проблемы, данные нуждаются в нормализации, нет гарантии ссылочной целостности и становится почти невозможно использовать такие мощные концепты как прототипы(prototypes ориг.).
MobX позволяет сделать управление состоянием вновь простым, вернувшись к корню проблемы: он делает невозможным инконсистентность состояния. Стратегия достижения этого довольно проста: убедится что, все что может быть вынуто из состояния, будет вынуто. Автоматически.
Концептуально MobX обрабатывает ваше приложение как электронная таблица (отсылка к офисной программе для работы с таблицами прим. пер.).
Во-первых, есть состояние State приложения. Графы объектов, массивов, примитивов, ссылок которые формируют модель вашего приложения.
Во-вторых есть производные Derivations. Обычно, это любое значение, которое может быть вычислено автоматически из данных состояния вашего приложения.
Реакции Reactions очень похожи на производные Derivations. Основное отличие: они не возвращают значение, но запускаются автоматически, чтобы выполнить какую то работу. Обычно это связано с I/O. Они проверяют, что DOM обновился или сетевые запросы выполнились вовремя
Наконец, есть действия Actions. Действия это все те штуки которые меняют состояние. MobX проследит, чтобы все изменения в состоянии приложения, вызванные действиями, автоматически обработались всеми производными и реакциями. Синхронно и без помех.
Простой todo store.
Довольно теории, рассмотрим его в действии, будет намного понятнее, чем внимательно читать написанное выше. Ради оригинальности давайте начнем с очень простого Todo хранилища. Ниже приведен очень простой TodoStore, который управляет коллекцией todo. MobX пока не участвует.
Мы только что создали todoStore инстанс с коллекцией todos. Теперь надо заполнить todoStore какими-нибудь объектами. Чтобы убедиться, что от наших изменений есть эффект, мы вызываем todoStore.report после каждого изменения:
Становимся реактивными
До сих пор в нашем коде не было ничего необычного. Но что если мы не хотим вызывать report явно, но объявим что нужно вызывать этот метод на каждое изменение состояния? Это освободит нас от обязанности вызывать этот метод в нашем коде. Мы должны быть уверены в том, что последний результат вызова report будет выведен на экран. Но мы не хотим беспокоится, каким образом это будет сделано.
К счастью, именно MobX может сделать это за нас. Автоматически вызывать код, который зависит от состояния. Так что наша функция report будет вызываться автоматически. Чтобы этого достичь TodoStore нужно стать отслеживаемым (observable ориг.), чтобы MobX смог следить за всеми изменениями. Давайте немного изменим наш класс.
Вот и все! Мы пометили некоторые свойства как @observable чтобы MobX знал что они могут изменяться со временем. Расчеты помечены @computed декораторами, чтобы знать что они могут быть вычислены на основе состояния.
Свойство pendingRequests и assignee еще не используются, но мы увидим их в действии чуть ниже. Для краткости, все примеры используют ES6, JSX и декораторы. Но не беспокойтесь, все декораторы в MobX имеют ES5 аналоги.
Делаем React реактивным
Следующий листинг показывает, что мы просто должны изменить наши данные. MobX автоматически вычислит и обновит соответствующие части вашего пользовательского интерфейса из состояния в вашем хранилище.
Работа со ссылками
Теперь у нас есть два независимых хранилища. Одно с людьми, другое с задачами. Чтобы назначить свойству assignee персону из хранилища с персонами, нам нужно просто присвоить значение через ссылку. Эти значения подхватятся TodoView автоматически. С MobX нет нужды в нормализации данных и написании селекторов, чтобы наши компоненты обновлялись. На самом деле, не имеет значения где хранятся данные. Пока объекты «наблюдаемы», MobX будет отслеживать их. Настоящие JavaScript ссылки тоже работают. MobX отслеживает их автоматически если они релевантны для производных значений.
Асинхронные действия
Так как все в нашем маленьком todo приложении является производным от состояния, то не имеет значения где это состояние будет изменено. Это позволяет достаточно просто создавать асинхронные действия.
DevTools
Пакет mobx-react-devtools предоставляет инструментарий разработчика, который может быть использован в любом MobX + React приложении.
Вывод
На этом все! Никакого бойлерплейта. Простые и декларативные компоненты которые формируют UI легко и просто. Полностью обновляются из состояния. Теперь вы готовы начать использовать пакеты mobx и mobx-react в вашем приложении.
Краткое резюме вещей которые вы сегодня узнали:
MobX не контейнер состояния
Люди часто используют MobX как альтернативу Redux. Но пожалуйста, обратите внимание, что это просто библиотека, для решения определенной проблемы а не архитектура или контейнер состояния. В этом смысле приведенные выше примеры являются надуманными и рекомендуется использовать правильные архитектурные решения, как инкапсуляция логики в методах, их организация в хранилищах или контроллерах и т.д. Или как кто то написал на Hacker News:
«Использовать MobX означает использование контроллеров, диспетчеров, действий, супервизоров или любой другой формы управления потоком данных, это ведет нас к тому, что архитектурную потребность вашего приложения проектируете вы сами, а не используя то что используют по умолчанию для чего то большего чем Todo приложение»
Заинтригованы? Вот некоторые полезные ссылки (на английском прим. пер.):
React + Mobx: в чём смысл?
Сегодня я хочу рассказать вам о том, как на нашем проекте состоялся переход на Mobx, какие преимущества это даёт. Также будет показан типовой проект и даны пояснения по основным вопросам. Но сначала вводные.
Почему вообще надо на что-то переходить? На самом деле, ответ на этот вопрос — уже половина дела. Многие сейчас любят применять новые технологии только потому, что они новые. Хорошая строчка в резюме, возможность саморазвития, быть в тренде. Здорово, когда можно просто идти вперёд.
И всё же, каждый инструмент должен решать свои задачи, и от них мы так или иначе отталкиваемся, когда пишем коммерческий код.
У нас в проекте существует некоторое количество виджетов, куда пользователь вводит свои данные, взаимодействует с формами. Как правило, в каждом виджете несколько экранов. Когда-то давно это всё работало на старом добром шаблонизаторе MarkoJS + обязательный jQuery на клиенте. Взаимодействие с формами писалось в императивном стиле, if… else, коллбэки и вот это всё самое то, что уже, кажется, осталось в прошлом.
Потом настало время React. Бизнес-логика на клиенте всё утолщалась, вариантов взаимодействий становилось много, императивный код превращался в сложно устроенную кашу. Декларативный react-код оказался гораздо удобнее. Можно было, наконец, сконцентрироваться на логике, а не представлении, переиспользовать компоненты и легко распределять задачи по разработке новых фич между разными сотрудниками.
Но и приложение на чистом React со временем упирается в ощутимые границы. Конечно, нам надоедает писать this.setState на каждый чих и думать про его асинхронность, но особенные трудности доставляет пробрасывание данных и коллбэков через толщу компонентов. Короче, настаёт момент окончательно разделить данные и представление. Не вопрос, можно исхитриться сделать это на чистом React, но в индустрии в последнее время популярны фреймворки, реализующие Flux-архитектуру фронт-енд приложения.
У нас, если судить по количеству статей и упоминаниям в вакансиях, наиболее известен Redux. Собственно, я уже занёс руку, чтобы инсталлировать его в наш проект и начать разработку, как в самый последний момент (и это буквально!) чёрт дёрнул полистать Хабр, а тут как-раз шло обсуждение темы «Redux или Mobx?» Вот эта статья: habr.com/ru/post/459706. Прочитав её, а также все комментарии под ней, я понял, что всё-таки буду использовать Mobx.
Итак, ещё раз. Ответ на самый главный вопрос – зачем всё это? – выглядит так: настало время разделить представление и данные, управление данными хотелось бы построить в декларативном стиле (как и отрисовку), никакого перекрёстного опыления коллбэками и пробрасываемыми атрибутами.
Теперь мы готовы приступить.
1. О приложении
Нам требуется на фронте построить некий конструктор экранов и форм, которые потом можно было бы быстро тасовать, соединять друг с другом вслед за меняющимися требованиями бизнеса. Это неизбежно подвигает нас к следующему: создать коллекцию абсолютно изолированных компонентов, а также некий основной компонент, соответствующий каждому из наших виджетов (по сути это отдельные SPA, создаваемые каждый раз под новый бизнес-кейс в общем приложении).
В примерах будет показана урезанная версия одного из таких мини-приложений. Чтобы не громоздить лишний код, пусть это будет форма из трёх полей ввода и кнопки.
2. Данные
Mobx по сути не является фреймворком, это всего лишь библиотека. В руководстве прямо сказано, что она не организует ваши данные напрямую. Вы сами должны придумать такую организацию. Кстати, мы используем Mobx 4, потому что версия 5 использует тип данных Sybmol, который, к сожалению, поддерживается не всеми браузерами.
Итак, все данные выделяются в отдельные сущности. Наше приложение стремится к набору из двух папок:
– components, куда мы положим все view
– stores, где будут содержаться данные, а также логика работы с ними.
Например, типовой компонент для ввода данных у нас состоит из двух файлов: Input.js и InputStore.js. Первый файл – это глупый компонент React, отвечающий строго за отображение, второй – данные этого компонента, правила работы с пользователем (onClick, onChange, etc. )
Прежде чем мы перейдём непосредственно к примерам, нужно решить ещё один важный вопрос.
3. Управление
Хорошо, у нас полностью автономные компоненты View-Store, но как у нас всё это срастается в целое приложение? Для отображения у нас будет корневой компонент App.js, а для управления потоками данных основное хранилище mainStore.js. Принцип простой: mainStore знает всё обо всех хранилищах всех нужных компонентов (ниже будет показано, как это достигается). Другие хранилища ничего не знают об окружающем мире вообще (ну, будет одно исключение — словари). Таким образом, мы гарантированно знаем, куда идут наши данные и где их перехватывать.
mainStore декларативно, через изменение частей своего state может управлять остальными компонентами. На следующем рисунке Actions и State относится к хранилищам компонентов, а Computed values относится к mainStore:
Начнём писать код. Основной файл приложения index.js:
Здесь видна основная концепция Mobx. Данные (stores) доступны в любом месте приложения через механизм Provider. Мы оборачиваем наше приложение, перечисляя необходимые в работе хранилища. Для использования Provider подключаем модуль mobx-react. Для того, чтобы основное управляющее хранилище mainStore со старта имело доступ ко всем остальным данным, делаем инициализацию дочерних хранилищ внутри mainStore:
Теперь App.js, скелет нашего приложения
Тут ещё две основные концепции Mobx – inject и observer.
inject внедряет только необходимый store в приложении. Разные части нашего приложения используют разные хранилища, которые мы перечисляем в inject через запятую. Естественно, подключаемые хранилища должны быть изначально перечислены в Provider. Хранилища доступны в компоненте через this.props.yourStoreName.
observer – декоратор указывает, что наш компонент будет подписан на данные, которые изменяются с помощью Mobx. Данные изменились – в компоненте возникла реакция (ниже будет показано, как). Таким образом, никаких специальных подписок и коллбэков – Mobx доставляет изменения сам!
К управлению всем приложением в mainStore мы ещё вернёмся, а пока сделаем компоненты. У нас их три вида – Fio, Email, Button. Пусть первый и третий будут универсальными, а Email – кастомный. С него и начнём.
За отображение отвечает обычный глупый React-компонент:
Мы подключаем внешний компонент валидации, и важно сделать это после того, как элемент уже включён в вёрстку. Поэтому метод из store вызывается в componentDidMount.
Теперь само хранилище:
Здесь стоит обратить внимание на две новых сущности.
observable – объект, любое изменение полей которого отслеживает Mobx (и передаёт сигналы в observer, который подписан на наше конкретное хранилище).
action – этим декоратором должен обёртываться любой хэндлер, который меняет state приложения и/или вызывает сайд-эффекты. Здесь мы меняем значение value в @observable params.
Всё, наш простой компонент готов! Он умеет отслеживать пользовательские данные и записывать их. Позже мы увидим, как центральное хранилище mainStore подписывается на изменение этих данных.
Теперь типовой компонент Fio. Его отличие от предыдущего в том, что мы собираемся использовать компоненты этого типа неограниченное количество раз в одном приложении. Это накладывает некоторые дополнительные требования на store компонента. В довесок, сделаем ещё подсказки по вводимым символам с помощью прекрасного сервиса DaData. Отображение:
Здесь есть кое-что новое: мы обращаемся к state компонента не напрямую, а через get:
Дело в том, что количество экземпляров компонента не ограничено, а хранилище одно на все компоненты этого типа. Поэтому при регистрации мы заносим параметры каждого экземпляра в Map:
State нашего универсального компонента инициализируется так:
С обычным объектом JS было бы удобнее работать, однако он не будет «прослушиваться» при изменении значений его полей, т.к. поля добавляются динамически при добавлении новых компонентов на странице. Получение подсказок DaData мы выносим отдельно.
Похожим образом выглядит компонент кнопок, только отсутствуют подсказки:
Компонент кнопки Button обёрнут HOC-компонентом ButtonArea. Обратите внимание, что старший компонент включает свой набор stores, а младший свой. В цепочках вложенных компонентов нет необходимости пробрасывать какие-либо параметры и коллбэки. Всё, что нужно для работы конкретного компонента, добавляется непосредственно в нём же.
Итак, у нас готовы все компоненты. Дело осталось за управляющим mainStore. Сначала весь код хранилища:
Ещё несколько ключевых сущностей.
computed – декоратор для функций, отслеживающих изменения в наших observable. Важным преимуществом Mobx является то, что отслеживаются только те данные, которые вычисляются в computed и потом возвращаются в качестве результата. Реакция и, как следствие, перерисовка virual DOM происходит только тогда, когда это необходимо.
reaction – инструмент для организации сайд-эффектов на основе изменившегося состояния. Он принимает две функции: первая computed, возвращающая вычисленное состояние, вторая с эффектами, которые должны последовать вслед за изменениями state. В нашем примере reaction применяется два раза. В первом мы смотрим состояние полей и делаем вывод о том, корректна ли вся форма, а также записываем значение каждого поля. Во втором мы по клику на кнопку (точнее, при наличии признака «кнопка нажата») отправляем данные на сервер. Объект с данными выводится в консоль браузера. Поскольку mainStore знает все хранилища, мы сразу после обработки клика кнопки можем позволить себе в императивном стиле отключить признак:
Можно обсудить, насколько допустимым является наличие такой «императивщины», но в любом случае управление происходит только в одну сторону — от mainStore к ButtonStore.
autorun используется там, где мы хотим запустить какие-то действия директивно, не в качестве реакции на изменение store. В нашем примере запускается одна вспомогательная функция, а так же предзаполнение полей формы данными из словаря.
Таким образом, последовательность выполнения действий у нас следующая. Компоненты отслеживают пользовательские события и изменяют своё состояние. mainStore через computed вычисляет результат, основанный только на тех state, которые изменились. Разные computed смотрят за изменением разных состояний в разных хранилищах. Далее через reaction мы на основе результатов computed выполняем действия с observables, а также выполняем сайд-эффекты (например, делаем AJAX-запрос). На obsevables подписаны дочерние компоненты, которые при необходимости перерисовываются. Однонаправленный поток данных с полным контролем над тем, где и что меняется.
Попробовать пример и код можно самому. Ссылка на репозиторий: github.com/botyaslonim/mobx-habr.
Дальше как обычно: npm i, npm run local. В папке public файл index.html. Подсказки DaData работают на моём бесплатном аккаунте, поэтому, вероятно, могут в некоторые моменты падать из-за хабра-эффекта.
Буду рад любым конструктивным замечаниям и предложениям по работе приложений на Mobx!
В заключение скажу, что библиотека очень упростила работу с данными. Для маленьких и средних приложений она точно будет очень удобным инструментом, чтобы забыть о свойствах компонентов и коллбэках и сконцентрироваться непосредственно на бизнес-логике.