Благодаря тому, что React предоставляет удивительные возможности по работе с отображением, можно сосредоточиться только на организации логики приложения и семантике кода, описывающего работу с данными. Т.е. выбирая state management библиотеку, происходит выбор стилистики будущей кодовой базы. В данной статье мы рассмотрим подход, основанный на сервисах-сторах, реализуемый благодаря библиотеке lamp-luwak.  TL;DR Кто хочет скорее приступить к написанию кода, может перейти к следующему параграфу. Вся логика внутри вашего приложения разбивается на сервисы, каждый сервис содержит в себе единственный стор, изменение которого уведомляет подписчиков. Так же внутри сервиса содержатся функции логики, где можно обращаться к другим сервисам и перезаписывать иммутабельный стор, эти функции могут быть асинхронными и содержать в себе сайд эффекты. lamp-luwak позволяет оптимизировать участки документа, необходимого для перерисовки. Локализация областей вашего отображения и минимизация затрат на обновления, происходит за счёт подписывания каждого React компонента вызывающего useProvide или useSubscribe на соответствующие сторы. Обновляться будут только те компоненты, что используют сторы, которые поменялись. Любой сервис можно инстанцировать в любом месте вашего приложения, для библиотеки не требуется какая-либо инициализация. Например, это особенно удобно для разработки изолированных компонентов или embedded решений. Или при внедрении в существующий проект. Также каждый сервис может быть подписан на изменение стора любого другого сервиса или набора сервисов через механизм subscribe. Таким образом можно делать зависимые вычисления. Для демонстрации стилистики кода воспользуемся примером приложения, что реализует часть функциональности todo-листа для сохранения наглядности: Развернём приложение todos используя create-react-app Так же устанавливаем пакет lamp-luwak в todos приложение Код организован на доступные по всему приложению сервисы, чтобы можно было в любом месте любого React компонента получить инстанцию какого-угодно сервиса и либо вывести его данные, либо инициировать какое-либо действие с этим сервисом. Сервис — это инстанция класса или функции-фабрики, представленная в единственном экземпляре, создаваемая при первом обращении и остающаяся до конца работы приложения.
Описывается ли сервис с помощью класса или функции-фабрики, внутри себя он имеет свойство store. При описании свойства store необходимо указать значение стора по умолчанию. Так же из этого значения будет автоматически выводиться тип стора для типизированных диалектов.
Далее сервис доступен либо из любого другого сервиса Здесь функция provide возвращает инстанцию сервиса, принимая в качестве аргумента класс сервиса, в данном примере Todo.
Либо из любого React компонента Идентификатором сервиса является его класс или функция-фабрика. В React компонент инстанция необходимого сервиса поставляется через метод useProvide, единственный аргумент которого — класс или функция-фабрика сервиса, в данном примере Todo. При таком способе получения сервиса в компоненте, компонент будет автоматически подписан на изменение стора этого сервиса. Стор — это иммутабельная структура данных, для модификации стора используется обычное присвоение, потому что свойство store после инстанциации через provide или useProvide становиться геттер/сеттером и если при присвоении приходит отличное от предыдущего значение, то все подписчики на изменение стора получат уведомление с новым и старым значением стора. Таким образом в примере выше, компонент List будет перерисовываться каждый раз когда будет изменяться значение стора сервиса Todo. Положим сервисы в папку services, а React компоненты в папку components и получим следующую структуру нашего приложения. Для реализации нашей задачи нам потребуется комбинация из 2-х сервисов. Это будет: Далее необходимо подробнее рассмотреть структуру стора Todo сервиса, это массив из экземпляров класса Task, т.е. это, по сути, массив из других сторов. Стоит упомянуть сразу, что никакой подписки на вложенные сторы не происходит, это просто допустимое значение. Экземпляр класса со стором может быть вложен в другой стор наравне с другими типами, такими как Date, Map, Set. (с SSR) Это сделано для оптимизации глубоких вложенных структур данных, чтобы локализовывать области обновления UI. А также для стилистики описания логики ближе к данным, к ней относящимся. Например, в нашем примере в Task содержится метод toggle, который меняет статус завершённости задачи. Для того чтобы создать экземпляр класса Task, со стором и возможностью быть источником уведомлений об изменении стора, необходимо использовать функцию create, принимающую первым аргументом класс или функцию-фабрику, а последующие аргументы будут проброшены в конструктор класса или в аргументы функции-фабрики соответственно. Создаём сервис Todo. В store по умолчанию положим два экземпляра задачи Task, создав их через функцию create. И добавляем метод add, который пересоздаёт стор, добавляя в него новый экземпляр задачи Task. В качестве уникального id, использовано значение вызова Date.now. Создаём класс Task, описывающий задачу. Описанная структура стора декларирует соответствующий формат данных: id задачи, её заголовок label и статус завершённости completed. Описываем метод toggle, что меняет значение статуса завершённости задачи на противоположное, используя функцию modify, что позволяет иммутабельно модифицировать стор, используя синтаксис с присвоением. Кроме описания класса присутствует ещё и описание экшена TaskChanged, созданного вызовом функции action. Экшен — это функция, которая может быть источником уведомлений для подписчиков. Экшен можно вызывать как функцию, но рекомендованный подход — использовать функцию dispatch библиотеки lamp-luwak. В коде выше, мы подписываем экшен на обновление стора, таким образом TaskChanged становится источником уведомлений об изменении стора каждого экземпляра класса Task. Подписка происходит посредством вызова функции subscribe, первый аргумент — это источник уведомлений об изменении стора, т.е. текущий объект, а второй — функция обработчик, что будет вызвана каждый раз при каждом обновлении стора. В данном случае в качестве функции обработчика выступает экшен TaskChanged, мы тоже экспортируем его из файла. Здесь мы создаём сервис TodoCounters, что является агрегатором. Он подписан на уведомления от 2-х источников: При каждом уведомлении происходит пересчёт счетчиков активных и завершённых задач в методе calculate. Графическая иллюстрация текущей структуры приложения:  Кроме основного компонента приложения App.tsx, присутствуют ещё четыре компонента. List — отрисовывает список задач. Для получения инстанции сервиса Todo, используется вызов useProvide, таким образом компонент List, не только получает возможность пользоваться стором, где хранится список задач, но и подписывается на модификацию этого стора. Компонент будет обновляться каждый раз, когда количество задач в списке будет меняться. Task — отрисовывает конкретную задачу. Чтобы подписаться на изменение стора экземпляра задачи Task, полученного в аргументах функции компонента, необходимо вызвать функцию useSubscribe, в результате компонент будет обновляться всякий раз когда будет изменён стор экземпляра задачи. Для изменения статуса завершённости, используется вызов метода toggle задачи. Counters — отрисовывает счётчики активных и завершённых задач. Компонент простой и не нуждается в излишнем описании, так как функция useProvide уже знакома. Здесь мы отрисовываем счётчики активных и завершённых задач, а так же подписываемся на изменение стора сервиса TodoCounters. Input — предоставляет форму для добавления новой задачи. Для добавления новой задачи используется метод add сервиса Todo. Для хранения локального значения формы, до момента добавления задачи в список Todo, используется стандартный хук React useState. И тогда содержимое файла App.tsx, находящегося в папке src проекта, станет подключением набора написанных выше компонентов. Enjoy! На этом наше приложение готово. Таким образом мы рассмотрели основные принципы построения приложений на lamp-luwak, и ознакомились с понятиями сервиса и стора, с понятием экшена и механизмом подписок. И попрактиковались в написании кода в новой стилистике. Кому понравилось, приглашаю самостоятельно сделать удаление задачи из списка. Конечно, стоит упомянуть, что библиотека ещё свежая, поэтому особенно приветствуется любая конструктивная информация об обнаруженных ошибках.
Всем спасибо за внимание. JavaScript, OpenSource enthusiast, entrepreneur