Привет, меня зовут Артём Арутюнян и я автор менеджера состояния Reatom. Этим постом открывается серия обучающих материалов на русском языке, документация на английском доступна на официальном сайте.
А оно вам надо? Думаю, да, потому что Reatom — это универсальное решение, которое позволяет легко пошарить глобальное состояние за микроскопическую (2.5KB) цену, эффективно строить самодостаточные и переиспользуемые логические модули гигантских приложений или просто сделать ваш сетевой кеш реактивным с помощью дополнительного пакета @reatom/async.
В этой статье мы кратко пройдёмся по мотивации и истории, а потом разберём основные фичи и примеры их использования вместе с биндингами к React.js. Похожий разбор есть в виде скринкаста.
▍ Мотивация
Я ленивый идеалист с каким-то пунктиком на перформанс (у меня часто были маломощные ноутбуки и смартфоны) и я всё время попадал на проекты с сотнями тысяч или миллионом строк кода, хотя иногда касался и мелких стартапов. И всегда мне хотелось использовать одно решение, не переключаясь между контекстами, которое будет и маленькое, и эффективное, и позволяло бы использовать энтерпрайзнутые паттерны, которые нужны, когда цена любого рефакторинга может быть огромной.
Мне нравилось, как реактивность решает проблемы связанности кода, а иммутабелность упрощает дебаг — это и стало главными столпами разрабатываемой библиотеки.
Сложно сделать хорошо всё и сразу, поэтому эволюция Reatom заняла годы.
▍ История
Первый релиз был осенью 2019-го, хотя ему предшествовали почти два года исследований. Началось всё в феврале 2018-го, тогда меня передёрнуло от function-tree, и я решил сделать с подобным апи убийцу редакса (тогда это было популярным занятием). Далее история долгая: погружение в дзен вывода типов TypeScript, десятки прототипов, постоянные попытки выжать лучшее из современных технологий. Исследование алгоритмов обхода графов для решения проблемы глитчей. Рост комьюнити и попытки писать понятную документацию. Обслуживание инфраструктуры монорепы. Погружение в теорию баз данных, которые я администрировал ещё в 2014-м, но не задавался вопросами подкапотной архитектуры. Переосмысление архитектуры веб-приложений и состояния как явления. Один из артефактов всего этого — недавняя статья «Что такое состояние», в которой изложены ключевые принципы архитектуры менеджера состояния.
Но главное — первая LTS и вторая версия реатома пытались быть совместимы с редаксом, и сколько я ни старался, нормально это сделать не выходило, он просто фундаментально сломан:
- O(n) сложность, где n — количество подписчиков;
- единая очередь для подписчиков и вычисляемых значений, из-за чего в селекторах нет атомарности;
- невозможность батчинга (диспатча нескольких экшенов).
Бойлерплейт для меня всегда был меньшей проблемой, но вы просто посмотрите на эту разницу между тулкитом(!) и реатомом. По ссылке используется пакет @reatom/framework, который включает в себя базовый набор самых часто используемых пакетов и просто реэкспортит из них всё для удобства установки и импорта. В последние пару лет требования к развитой экосистеме всё важнее. В 2020-м было нормально иметь маленькую библиотеку и дать на откуп пользователей писать и публиковать в NPM свои хелперы. Но сейчас индустрия уже повзрослела и предъявляет взвешанные требования к экосистеме, её слаженности и поддержке. Все пакеты Reatom хранятся в монорепе, что позволяет тестировать любое изменение со всеми зависимостями и синхронизировать релизный цикл, сделав его предсказуемым.
Это что касается технического аспекта поддержки, в общем же политика выглядит так: нечётные релизы считаются LTS и поддерживаются несколько лет. Первая версия поддерживалась три года, сейчас можно подменить импорты и использовать код на ней дальше с новыми фичами и дальнейшей поддержкой. Текущая третья версия (@reatom/core@3.x.x) будет актуальна ещё несколько лет. Раз в год возможны небольшие ломающие изменения от рефакторинга типов.
▍ Базовые сущности
Cперва взглянем на код базового примера из этой песочницы:
import { action, atom } from '@reatom/core'
import { useAction, useAtom } from '@reatom/npm-react'
const inputAtom = atom('')
const greetingAtom = atom((ctx) => `Hello, ${ctx.spy(inputAtom)}!`)
const onChange = action((ctx, event) =>
inputAtom(ctx, event.currentTarget.value),
)
export const Greeting = () => {
const [input] = useAtom(inputAtom)
const [greeting] = useAtom(greetingAtom)
const handleChange = useAction(onChange)
return (
<>
<input value={input} onChange={handleChange} />
{greeting}
</>
)
}
Это самый базовый пример трёх ключевых сущностей: контекст, атом, экшен. На их основе можно реализовать большинство популярных паттернов из ФП или ООП и расширять по необходимости дополнительными фичами, больше десятка существующих пакетов этому пример. Но давайте разберём каждую строчку детальней.
Конечно, больше всего вопросов вызывает
ctx
. Это некий глобальный DI-контейнер на стероидах, заточенный под стейт-менеджемент. Он позволяет читать актуальный стейт атома ctx.get(anAtom)
, подписываться на него ctx.subscribe(anAtom, newState => sideEffect(newState))
и планировать сайд-эффекты во время транзакции, но об этом попозже. Главное, что нужно запомнить — ctx
прокидывается первым аргументом в большинстве колбэков реатома и каждый раз приходит новый (под капотом содержит весь стек предыдущих контекстов вплоть до глобального).inputAtom
— базовый атом и кирпичик, с которого всё начинается. Это переменная для реактивного доступа и обновления данных. Хорошей практикой является разделять все ваши состояния на множество атомов с примитивными значениями. Для пользователей редакса это может быть чуждо, но в процессе использования всё больше будет ощущаться профит от такого подхода.Базовый атом может быть вызван как функция с новым значением или редьюсером этого значения:
countAtom(ctx, 1)
и countAtom(ctx, state => state + 1)
. Такой вызов функции возвращает новое значение атома. В типах такой атом называется AtomMut
(mutable atom).Благодаря такому апи код быстро и удобно парсить глазами:ctx.get
иctx.spy
получают значение атома, аdoSome(ctx)
иsomeAtom(ctx, value)
мутируют.
greetingAtom
— вычисляемый атом, который вызывает переданную функцию при первой подписке и с помощью метода spy
в контексте подписывается на переданный атом и получает его значение. Это map
и combine
в одном флаконе, только гибче и удобнее. В типах такой атом называется просто Atom
. У вычисляемых значений реатома есть две киллер-фичи, каждая из которых отдельно встречается в ФП (редакс) или ООП (мобыкс) мире, но я ещё не встречал их вместе.Первое — если вычисление в редьюсере (да, вторым аргументом приходит предыдущий стейт) упадёт с ошибкой, все предыдущие изменения в текущей транзакции откатятся и будет соблюдена атомарность (рассказывал об этом здесь). Это важный аспект, позволяющий не допустить неконсистентные данные, и он важен для крупных приложений. Базовое поведение React практически такое же, даже жёстче, всё приложение размонтируется (в случае отсутствия
componentDidCatch
).Второе — вы можете использовать
spy
в любом порядке (привет, правила хуков реакта). Вы можете применять его в условии и подписываться только на нужные атомы, когда это действительно актуально, что оптимизирует автоматически ваши потоки данных и помогает избавится от лишних вычислений.const listAtom = atom([])
const listAggregatedAtom = atom(ctx => aggregate(ctx.spy(listAtom)))
const listViewAtom = atom((ctx) => {
if (ctx.spy(isAdminAtom)) {
return ctx.spy(listAggregatedAtom)
} else {
return ctx.spy(listAtom)
}
})
Подобный код на реселекте или ФРП-библиотеке, скорее всего, получал бы вместе
isAdmin
, list
и listAggregated
и способствовал избыточным вычислениям (для isAdmin === false
). Конечно, в теории можно описать селекторы, которые будут делать примерно то же самое, но на практике так не заморачиваются и получают очередную каплю в замедление приложения. В реатоме такие условные подписки — базовый принцип.Удобно использовать в вычисляемом атоме и простой
ctx.get
для чтения какого-то значения — это не создаёт подписку, но гарантированно отдаёт самый актуальный стейт переданного атома.На самом деле атомы не хранят значения, а являются лишь объектом с метаданными и ключом WeakMap-контекста, где и хранятся все стейты и связи между атомами. Это позволяет прозрачно и безопасно инстанцировать цепочки вычислений и упрощает SSR и тестирование.
onChange
— экшен, хелпер для батчинга изменений. Если у вас есть несколько атомов для последовательного обновления, каждое изменение будет тригерить их зависимые вычисления и подписчиков. Что бы забатчить вычисления, можно использовать колбэк в ctx.get(() => {...})
или просто создать выделенный экшен и произвести все апдейты в нём. Экшены удобны тем что им можно, как и атомам, давать имена (второй аргумент), что в дальнейшем упрощает дебаг @reatom/lgger.Про TypeScript, реатом разрабатывается с большим фокусом на автоматическом выводе типов и всегда старается понять переданные данные. Если вам необходимо затипизировать параметры экшена, просто укажите их тип у них же: action((ctx, event React.ChangeEvent) => ...). Больше рекомендаций по описанию типов ищите в документации.
Под капотом экшен — это атом со временным стейтом, хранящий params вызова и возвращённый payload. С ним можно делать всё то же, что и с атомом: подписываться черезctx.subscribe
для сайд-эффектов иctx.spy
в вычисляемом атоме. Например, можно в вычисляемом атоме получить данные другого атома только при срабатывании какого-то экшена — это редкий, но очень удобный способ оптимизации. Больше примеров и возможных паттернов разберём в следующих статьях.
Стоит упомянуть о
ctx.schedule
, который позволяет планировать сайд-эффекты, как useEffect
в реакте. Его можно вызывать где угодно, но чаще всего это пригождается в экшенах.
const onSubmit = action((ctx) => {
const input = ctx.get(inputAtom)
inputAtom(ctx, '')
ctx.schedule(() => api.submit({ input }))
})
Переданный в
ctx.schedule
колбэк будет вызван после всех чистых вычислений, но до вызова подписчиков — это удобно, т. к. иногда эффекты просто сохраняют что-то в localStorage или делают другие не чистые, но синхронные операции и вызывают ещё апдейты. Подробности есть в документации, в общем же реатом старается всегда максимально отложить вызов подписчиков, чтобы избежать лишних ререндеров и предоставить самый последний и актуальный стейт. У редакса с этим ситуация радикально хуже.▍ @reatom/npm-react
Все пакеты-адаптеры имеют префикс платформы (npm
,web
,node
), подробнее об этом можно почитать в документации.
Думаю, использование
useAtom
и useAction
понятно и практически не нуждается в комментариях :) Хотя несколько вещей всё же нужно учесть.В документации к npm-react описаны обязательные инструкции по подключению реатома в провайдер реакта и настройке батчинга для старой (<18) версии реакта.
import { createCtx } from '@reatom/core'
import { reatomContext } from '@reatom/npm-react'
const ctx = createCtx()
export const App = () => (
<reatomContext.Provider value={ctx}>
<Main />
</reatomContext.Provider>
)
Почему
useAtom
возвращает кортеж, как useState
? Потому что его можно использовать как useState
! Вторым значением приходит колбэк обновления, который принимает новое значение или редьюсер.export const Greeting = () => {
const [input, setInput] = useAtom(inputAtom)
const [greeting] = useAtom(greetingAtom)
return (
<>
<input value={input} onChange={e => setInput(e.currentTarget.value)} />
{greeting}
</>
)
}
Конечно, это будет работать только для
AtomMut
— невычисляемого атома с примитивным начальным значением.Но не будем на этом останавливаться. Вы можете не создавать отдельный атом и использовать его в разных местах через импорты, а можете использовать примитивное значение в
useAtom
, и атом под капотом будет создан автоматически, а ссылку на него можно получить в третьем элементе кортежа. Также вы можете передать и вычисляемый редьюсер в useAtom
, как и в обычный atom
, и описать какой-то селектор прямо в компоненте.
export const Greeting = () => {
const [input, setInput, inputAtom] = useAtom('')
const [greeting] = useAtom((ctx) => `Hello, ${ctx.spy(inputAtom)}!`, [inputAtom])
return (
<>
<input value={input} onChange={e => setInput(e.currentTarget.value)} />
{greeting}
</>
)
}
Это может быть удобно для некоторых оптимизаций, смысл которых в передаче получившегося атома по ссылке в пропсы нижележащим компонентам. Подробнее этот паттерн мы разберём в следующих статьях.
▍ Заключение
Это всё, чем хотелось поделиться в первой статье, но реатом таит в себе ещё множество фич и архитектурных практик, которые сильно помогают в написании более чистого и тестируемого кода. Но обо всём поэтапно, ждите серию постов.
Можно лишь отметить, что любителям ФРП стоит обратить внимание на пакет @reatom/lens, а сторонникам более классической архитектуры — взглянуть на пакет @reatom/hooks, который позволяет писать более изолированный код, приближенный к акторам.
Ах да, и про реактивный кеш. Пакет @reatom/async в связке с базовыми фичами реатома даёт большую часть фич react-query, а какие-то даже превосходит, всего за 3.4KB (gzip).
Смотрите больше примеров на соответствующей странице документации, добавляйтесь в Телеграм-канал и чат. И, конечно, оставляйте ваши комментарии и вопросы ниже.
Играй в нашу новую игру прямо в Telegram!
Комментарии (59)
gev
03.01.2023 13:31Сравнивали с Recoil?
artalar Автор
03.01.2023 13:37Не интересно, тк он прибит к реакту. Если это не смущает, лучше на jotai посмотреть - проще и легче рекоила.
yroman
03.01.2023 13:55Библиотека интересна, но пока не особо ясно как к ней подступиться. Поэтому вопросы:
Сколько разработчиков на проекте и какова вероятность, что вы забросите проект из-за каких-либо обстоятельств? Ежу понятно, что никто в здравом уме не станет тащить решение одиночки в коммерческие проекты и завязывать на это всю архитектуру.
Хотелось бы интересных примеров более серьезных приложений, где используется ваш подход. Очевидно, что приведенные в статье примеры, ммм, не слишком впечатляют. Хотелось бы лучших практик построения моделей, взаимодействия и так далее. У вас же должно быть видение? Для редакса и мобикса это все есть.
artalar Автор
03.01.2023 14:22Реатом давно используется в проде в больших и маленьких компаниях, иногда и вакансии с ним мелькают.
Забрасывать его не планирую, этот проект мне очень дорог, но честно говоря сейчас бас фактор - я один.
Реатом старается быть максимально примитивным: есть атомы для значений, есть экшены для логики - играйтесь. Дальше можно строить и ФП и ООП, впринципе свой фреймворк. Я предпочитаю простую процедурщину с минимум абстракций. При этом из-за обязательных принципов к иммутабельности, выделению эффектов в отдельный слой и еще пачке рекомендаций совсем жесть написать будет сложно, наивно код просто получается нормальным.
Примеры больших проектов я считаю смотреть безсмысленно, у каждого своя специфика. Но вот в опенсурсе есть один такой: https://github.com/konturio/disaster-ninja-fe
yroman
03.01.2023 15:15Хм, а можно примеры таких компаний? Я хабр читаю давно, а вот про reatom услышал только недавно. Тот же моль уже здесь всем оскомину набил давно.
Не могу согласиться насчет примеров проектов, все равно есть явные схожие паттерны, но за ссылку спасибо, посмотрю.
artalar Автор
03.01.2023 15:26За последние несколько лет вот эти компании точно использовали реатом: Яндекс, A3, sravni.ru, consta.design, и еще пачка о которых я не помню / не знаю. У самого сейчас reatom/async используется на проекте в пол ляма строк.
Последние несколько лет реатом находился в постоянной разработке - поэтому я его сильно не пиарил. Текущая версия меня удовлетворяет достаточно, что бы на ближайшие пару лет сфокусироваться только на разработке экосистемы, теперь я могу смело говорить что это универсальное и надежное решение с чистой совестью.
orekh
04.01.2023 07:36Примеров бы побольше простых, вроде связывания двух полей ввода на чистом яваскрипт, типа конвертер величин. Без всяческих магических реактов и jsx - и без них достаточно того что читатель пытается разобраться в реатом, о котором автор умалчивает что такое есть методы "получить", "шпионить", "подписаться" - чем похожи а чем отличаются? когда использовать а когда нет? - неясно.
nin-jin
03.01.2023 15:13+6если вычисление в редьюсере (да, вторым аргументом приходит предыдущий стейт) упадёт с ошибкой, все предыдущие изменения в текущей транзакции откатятся и будет соблюдена атомарность (рассказывал об этом здесь). Это важный аспект, позволяющий не допустить неконсистентные данные, и он важен для крупных приложений.
К чему приводит пресловутая атомарность селекторов можно наблюдать в этом примере: когда гуляешь по карте довольно скоро всё приложение замирает и перестаёт реагировать на пользователя вообще. А ведь год назад я объяснял почему такое поведение плохо. Особую пикантность ситуации придаёт отсутствие ошибок в консоли.
artalar Автор
03.01.2023 15:17Причем тут атомарность вообще не ясно. В любом случае, у меня ничего не замирает и потребление памяти вообще не меняется, "гулял" 1 минуту. Как воспроизвести проблему?
nin-jin
03.01.2023 15:20+2Поправил ссылку.
artalar Автор
03.01.2023 15:28Так что с ней делать?
nin-jin
03.01.2023 15:33+1Открыть и увидеть зависшее приложение через секунду использования.
artalar Автор
03.01.2023 15:39Не получается (( все работает, хотя жрет пол гига ОЗУ.
yroman
03.01.2023 15:42+1К сожалению, действительно виснет. Win 11, Firefox 108.0.1. Можно немного продвинуться по лабиринту, а потом всё.
nin-jin
03.01.2023 16:25stackblitz возможно что-то закешировал слишком агрессивно. Возможно поможет очистка локального хранилища.
artalar Автор
03.01.2023 16:52Воспроизвел. А что ты там сделал? Оригинальный сендбокс работает https://stackblitz.com/edit/effector-vite-react-template-3p6t2a?file=src%2Fmodel.ts, а по твоей ссылке твой форк.
nin-jin
03.01.2023 16:57Добавил тривиальную багу. Счастливой отладки.
strokoff
03.01.2023 19:31-3Спасибо, за отличный пример. Пометил себе реатом в мертвооожденные. Обожаю хабр за это, один на серьёзных щах пердлагает ганвокодить по его идеологии, а комьюнити просто показывает, что поделка не работает и желает счастливой отладки! Лучше аргумента и не придумать, браво)
markelov69
04.01.2023 13:15-3Пометил себе реатом в мертвооожденные
Полностью поддерживаю
Обожаю хабр за это, один на серьёзных щах пердлагает ганвокодить по его идеологии
Во во, и самое смешное и печальное что остальные этот бред подхватывают
Pijng
03.01.2023 22:52Вы же потом отпишите – в чем баг то был!
nin-jin
03.01.2023 23:11Его не сложно найти с помощью отладчика. Типичный для чистой функции баг со внезапным NPE при некоторых входных значениях, которые не проверены в тестах.
Pijng
04.01.2023 16:00В общем-то нашел, понятно – в
$playerBlock
индекс вне границ будет.
Энивей немного странно слышать комбинацию из "чистой функции" и "npe". Надеюсь курсивом не просто так выделено.
А что предлагают другие решения в таких ситуациях?
artalar Автор
04.01.2023 23:39В самих вычислениях catch. делать не нужно, там впринципе намеренно никогда не должны исключения кидаться, соответственно и ловиться. Все кетчи над вызовом акшена / апдейта атома в асинхронном контексте (из ui-эвента или после await).
schedule нужен что бы планировать сайд-эффекты из экшенов.
Alexandroppolus
04.01.2023 00:52вы можете использовать
spy
в любом порядке (привет, правила хуков реакта). Вы можете применять его в условии и подписываться только на нужные атомы, когда это действительно актуально, что оптимизирует автоматически ваши потоки данных и помогает избавится от лишних вычислений.Мобх умеет так делать даже в реактовских компонентах. А вот Реатом, кажется, нет: useAtom подписывается безусловно.
artalar Автор
04.01.2023 05:18Это хорошее замечание, реатома пока так не умеет делать внутри компонента, но я работаю над этим (не думал что эта фича приоритетная).
markelov69
Getters / Setters - не, не слышал. Буду делать так, чтобы пользоваться было максимально неудобно.
До MobX'a конечно ещё очень и очень далеко.
artalar Автор
Геттеры и сеттеры вносят избыточную семантику и сложность, они менее явные и я всегда стараюсь их избегать. Дебаг мутабельных данных намного сложнее, а в реатоме из коробки асинхронный трекинг контекста-стека, чего мобыксу скорее всего никогда не получить. Про бандлсайз и так ясно, из-за этого мобыкс очень далек от универсального решения, но и перф у него до двух раз хуже... Очень специфическое решение, этот ваш мобыкс. И экосистема у него не большая и не развивается.
markelov69
С чего это вдруг? Неправда.
Где тут избыточность и сложность? Геттеры и сеттеры под капотом, они не используются при написании кода.
Взгляните на пример с кодом выше, в каком месте вы видите хоть грамм не явности?
Да ладно? А может вы в блокноте код пишете?)
Webstorm:
Клик правой кнопкой -> Find Usages
VS Code:
Клик правой кнопкой -> Find All References
Итого: за 5 секунд нашли все места где читается и где изменяется нужная нам переменная, если сразу не понятна причина бага, то добавляем console.log в нужные места и вуаля, мы элементарно нашли причину бага.
А да? Т.е. React + React-DOM размером в более чем 150kb это норм, а MobX добавляющий ещё пару копеек, это уже проблема? Не смешите. Или вы всё ещё в 2010 году живете?
О, это великая проблема если в цикле из миллиона итерацией он окажется на сколько-то ms медленнее. Это же прям относится к дело и в реальной жизни мы такие проекты и пишем.
Вообще кошмар :D
Зачем экосистема полностью готовому, самодостаточному, законченному и работающему проекту? Просто бери его и вперед.
Pijng
Есть такая профессия – MobX любить.
artalar Автор
Так, ну мне теперь очевидно что вы видите исключительно то что хотите видеть, но по аргументам вашим пробегусь, для интереса других читателей.
Нельзя добавить логики и не уменьшить семантику, это база в теории ЯП и инженерии. Мобыкс предполагает скрытое добавление семантики с которой нужно жить и учитывать: присвоение проперти в переменной не делает ее реактивной, геттеры в форыче могут заметно повлиять на перф, и в общем где-то можно не намеренно подписываться на то что не нужно, например ридонли
id
для `key` в рендеринге списка элементов.Это не катастрофическая проблема, просто, по моему мнению, избыточная.
Совсем печально что вы не знаете ответ на этот вопрос. Мобыкс, в зависимости от настроек, может не забатчить несколько последовательных апдейтов после авейта.
Зачем мне этот пример? В реальном приложении все сложнее. Кстати, в мобыксе нет сущности event / action и нельзя построить событийную модель, а иногда надо. Например, как вы будете отправлять лог аналитике при клике по кнопке? Засунете этот код прям в хендлер? Грязно, грязно!
Далее. Как вы связали дебаг и инспектирование кода я не совсем понял, но уточню что речь об инспектировании результата асинхронных потоков: в мобыксе с этим сложнее, в реатоме просто работает из коробки даже на проде 9и ничего не стоит, сами внутренние структуры данных так задизайнены).
Ну вам норм, но не все на реакте пишут. Реатом предоставляет адаптер для свелта, в разработке адаптер для преакта и солида. Кто туда и зачем возьмет мобыкс - не ясно, а вот реатом сможет пригодиться.
Ну тот же
@reatom/async
очень удобен, в мобыксе такого не хватает. А еще у нас почти готовы пакеты для офлайн синхронизации и форм, позже будет пакет для роутинга. Намного лучше когда это все разрабатывается одной группой лиц.markelov69
Бла бла бла, много воды и всё не по делу. Только 1 "аргумент", который легко проверить.
Вот он:
Проверяем:
Вывод: Ваш "аргумент" это просто пустые слова не подтвержденные практикой и реальными проектами и задачами.
Да-да, конечно же я ничего не знаю и вообще первый день в разработке.
Ой, а вот и автобатчинг.
Да, засуну прямо в хэндлер т.к. это самое очевидное что может быть и в любой момент времени, любой разработчик будет понимать что происходит на каждой строчки кода.
А грязно это вот этот ваш лютый говнокод:
Бла бла бла, опять много слов и ничего по делу. Речь идёт о мухах в недрах снежной массе вещества в коробке передач.
Так пусть пишут, мне то что с того.
Вообще нет, это просто очередная лапша. А все async хэлперы реализуются элементарно с помощью MobX.
И вообще взрослые дядьки пишут плагины и трансформеры, чтобы оставлять исходный код красивым и чистым, а на этапе "компиляции" уже его модифицировать как нужно, например заворачивать компоненты в observer'ы, например разруливать race condition'ы, debounce'ы и т.д и т.п. и при этом чтобы не приходилось писать убогие конструкции и лапшу. А просто бизнес логику сверху вниз, слева на право.
DonVietnam
Вы хоть представляете как в вашем супер тесте поведет себя движок JS? Рискну предположить, что запускали вы его в V8, что означает, что цикл ваш он скорее всего на одном месте повертел и удалил в целях оптимизации, нельзя так мерить производительность в JS.
nin-jin
Зачем вы спорите с человеком, который не первый день в разработке? Очевидно же, что мобыкс не делает в геттере ничего кроме чтения одного поля.
markelov69
Вот вам прямо с MobX'ом
nin-jin
1 миллисекунда против 15 микросекунд на 20к итераций.
Alexandroppolus
Как это бестпрактисно выглядит на Реатоме?
Имеется довольно неплохой mobx-utils, там есть несколько асинхронных паттернов. Ну а что-то совсем кастомное запилить тоже нетрудно. Мобх - это удочка, можно взять и наловить любую рыбу.
artalar Автор
Архитектурный смысл реактивности в неявном связывании для лучшей изоляции каждого модуля. Аналитика - совсем отдельная задача и было бы хорошо что бы она подключалась со стороны и лениво, не трогая код фичи и не влезая в логику. Реактивное программирование именно про это - мы берем паблик интерфейсы с сигнатурой subscribe и добавляем где-то вне реакции.
Те модуль аналитики просто импортирует нужные атомы / экшены и подписывается на них. В данном примере это будет так
```javascript
// anal.ts
import { onUpdate } from '@reatom/hooks'
import { onSomeClick } from '~/features/some/model'
onUpdate(onSomeClick, () => log('onSomeClick'))
```
Я так делал, вполне удобно, код фичи становится заметно чище.
Это наивное решение. Есть еще возможность собирать логи автоматически - подписаться вообще на все логи через `ctx.subscribe((logs, error) => logs.forEach((patch) => patch.proto.name && log(patch.proto.name, patch.state)))`, но тут нужно побольше фильтров наставить каких-то, конечно.
nin-jin
При реализации фичи придётся править ещё и код аналитики в совсем другом месте.
Аналитика потянет в бандл все фичи, даже те, что не используются.
artalar Автор
Ну давайте теперь вообще код не изолировать и писать огромный монолит? Очевидно же что код разбивается на внутреннюю реализацию и внешние интерфейсы и последние реже меняются и свободнее переиспользуются в других модулях, тестах, аналитике.
Зачем аналитике тянуть в бандл лишние фичи тоже не ясно, есть очевидные способы разделения кода на чанки.
nin-jin
Какие-то странные крайности. Ну вот аналитика получается огромным монолитом, которая знает о всех 100500 экшенах белее 9000 модулей.
Потому, что она их все импортирует к себе.
Alexandroppolus
https://mobx.js.org/analyzing-reactivity.html#spy
Там же, кстати, и для дебага тулзы
artalar Автор
ну вот проблема в том что в мобыксе все равно нет эвент-лайк сущности и сам клик по кнопке в мобыксе никак не отметится. Реатом, с моей точки зрения, в этом плане лучше архитектуру навязывает.
nin-jin
В интерфейсе может быть несколько кнопок, которые выполняют одно и то же действие. В аналитике обычно хотят знать по какой именно кнопке был клик, а не только какое действие было выполнено где-то в недрах приложения.
artalar Автор
Эм, а у одних и тех же кнопок будет один и тот же хендлер?? Если логику строить на определении атрибутов в event.target - да, но таким редко кто страдает (и зачем?).
В любом случае, в модуле аналитике лог будет выглядеть так `onUpdate(onClick, (ctx, { params: [event] }) => log('click', event.target.dataSome))`
Alexandroppolus
Отслеживать пользовательские действия - это, вообще говоря, не задача для стейт-манагерской либы.
Отслеживание кликов я бы сделал старым добрым делегированием, с дата-атрибутом. Просто, декларативно, навешивается сбоку. Единственный изъян - stopPropagation будет мешаться, ну да он давно признан антипаттерном и практически не юзается.
nin-jin
Сравните с этим:
e_Hector
а где в вашем варианте обработка ошибок?
nin-jin
А они сами обрабатываются, прикладнику о них думать постоянно не нужно.
e_Hector
А как именно обрабатываются?
Что делать, если в зависимости от ошибки нужно показать какое-то сообщение пользователю?
nin-jin
Если надо что-то кастомное, то заворачиваем в try-catch и делаем всё, что захотим:
nin-jin
Это как?
artalar Автор
https://www.reatom.dev/guides/debug