Потому, что когда вырастаешь в юниора, то начинаешь понимать, что TypeScript — это костыли для стажёров, а для вырастания в ведущие надо уже понимать, что плыть против течения себе дороже. Все побежали, и я побежал. ;)
У нас бекэнд общается с овердохрена сторонних сервисов, каждый из которых тоже апдейтится и может вдруг повести себя неадекватно.
Может. Но это сторонние для вас сервисы. Этот замусоленный уже до дыр пример с Adblock был о другом: представьте, что у вас в своём облаке, своих контейнерах, своих сборках и своей JVM всё открыто нараспашку и можно внедрить любой сторонний код. Ну т.е. вообще любой, и в любой момент.
А если мы вдруг курс валюты не проверим (по эвристикам и прочим ИИ) перед совершением транзакции — можно влететь на десятки миллионов евро.
Ну вот именно об этом я и талдычу уже который день: вы контролируете эти условия. Вы можете реагировать на внешние события и нейтрализовать их в своём коде или ещё как-нибудь. Что-то сломалось? Починили. Ненадёжный источник данных? Обложили эвристиками, проверками и перепроверками. Это контроль над средой. Не идеальный, но контроль.
А у нас иногда проблему можно решить, а иногда нет. Вот никак. В гугле правила SEO тихо поменялись, и всё, вашему стартапу пришёл он самый, белый и пушистый. Лично знаю людей, которые на этом потеряли миллионы зелёных денег, и повезло, что не весь бизнес.
Поэтому проблемы визуализации — это неприятно, но гораздо проще и безопаснее, чем проблемы бизнес-логики на бекэнде.
Эту старую песню о главном можно продолжать очень долго, но front end это не только визуализация данных.
Вообще говоря, эта дискуссия ни о чём, и ни к каким выводам не приведёт, а обед давно уже остыл. Честно, давайте правда уже закончим. Всего хорошего.
Да ну ладно, о чём вы в самом деле? О какой анонимности тут речь идёт, когда товарищ авторитарно так заявляет, что его нужно слушаться ну вот нипочему? Мне просто любопытно, что это за птиц такой, всезнающий, всеумеющий и в истину в последней инстанции вхожий. А то чем чёрт не шутит, соберёшься этак собеседоваться в $куда-нибудь, а там как раз слушаться надо. А пацаны-то и не знали.
Что касается профиля на этом сайте, то мне он не настолько важен, чтобы время тратить. Какое там качество аргументации, хабр это наискось за обедом полистать и языком почесать между делом, пока код в голове варится. А если вам интересен мой профиль, то в гугле вторая ссылка на мой полудохлый бложек, а четвёртая на Github. В отличие от, кстати.
Но ведь бывает и так, что бэк — это сотни а то и тысячи задач,
Бывает, конечно бывает. И так бывает, и сяк бывает, хаос и энтропия никого не оставят в покое. Просто я всё время наблюдаю со стороны, как жизнь идёт у наших back end/platform/devops товарищей, которые всё время ужа с ежом скрещивают… Спокойнее у них там, гораздо. :)
Иногда даже подумываю, а не плюнуть ли на всё, натянуть Golang какой-нибудь на голову и податься через забор. Но скучно там всё же, когда привык к адреналину, размеренная жизнь как-то не цепляет уже. Да и плюшки с этой стороны вкуснее. :)
Вы меня не поняли. На бэке неконтролируемые условие — это тоже обыденность, и по влиянию они точно такие же, как и adblock. Где-то кто-то что-то поменял и больше оно у вас не работает.
В таком случае я не вижу, как это противоречит заявлению "front end это хаос". Даже более: поздравляю, у вас тоже хаос. :))
Но вам все равно кажется, что на бэке трава зеленее, ибо там нет адблока. Забывая про других личностей, которые делают ровно ту же грязь, и имя им легион.
Дался вам этот adblock, это просто один пример был. :) Мысль-то была очень простая: в front end разработке настолько часто встречаются неконтролируемые изменения условий, приводящие к кардинальным изменениям в софте и процессах, что это является нормой вещей. Делали приложение для десктопа и современных браузеров? Поздравляю, мы вчера подписали контракт на $1005000 с XYZ и им надо IE11. Упс, а Chrome пять минут назад обновился и теперь скроллинг не работает, срочно фиксить. А что, у вас сайт на телефонах не работает? Какая разница, что в ТЗ было, это вчера было! Что? Гугл поднял расценки на внедрённые карты, а у нас на них весь интерфейс завязан?
Ну и т.п. Куда уж тут до чуточку недопереоптимизированных алгоритмов, выкатить бы что-нибудь. :)
Неужели вы думаете так только на фронте? Даже когда софт пишется внутри компании, ровно те же самые проблемы: алгоритмы недодуманы, одна фича конфликтует с другой, таски переписываются и перетасовываются, требования меняются прямо на релизе, и т.д.
Я вам даже больше скажу: так вообще везде. Помнится, в начале 2000х, когда ВТБ по случаю прикупил себе Гута-банк, мне пришлось разворачивать колл-центр ВТБ24 с оборудования Гуты, но на площадке ВТБ. Сроки сжатые, нужное количество E1 пробросить не успевали, поэтому можно было только Voice-over-IP, новинку по тем временам. За неделю прошли все инстанции (в ВТБ! представляете?!), всё подписали, всё согласовали — стыковали сети, пограничку настроили, всё работает. Ещё за неделю телефоны развернули, сценарии прописали, всё готово к сдаче.
А потом накануне запуска безопасники ВТБ такие: а мы тут вот затылок почесали, короче, не нравится нам RTP этот ваш, небезопасный он. Вы там что-нибудь придумывайте, а мы вам порты на файрволле зарубили. Всего хорошего.
Вот 15 лет назад было, а до сих пор помню. Потому что такое резкое и несовместимое с разумом изменение внешних условий всё же не такое частое явление… Вне front end. :)
Поверьте, любая третья сторона создает абсолютно одинаковые проблемы, хоть адблоком она называется, хоть админом Йоханом из Дели, хоть юзером Мартой из Бостона.
Да ладно, ну что вы в самом деле. Проблема же не в разнородности проблем, а в их количестве. Как кто-то когда-то сказал, Quantity is a quality of its own. :)
В общем, не хочу показаться невежливым, но мне кажется, что дискуссия себя исчерпала. Давайте на том и закончим, всего хорошего.
То есть форма перерисовывается вся при изменении любого одного контрола, потроха таблицы после Bind — тоже все разом.
Так они и обязаны перерисовываться, потому что зависят от состояния ключей filter, page и limit. Напрямую зависят. Я не вижу, в чём здесь проблема.
Чтоб перерисовывать только то, что меняется (как это и происходит в вариантах с MobX) — вам нужно тащить ViewModel вниз.
Можно и утащить, если есть необходимость. Можно добавить кучу оптимизаций, чтобы не пересчитывать values каждый раз, мемоизировать формулу, например. Вопрос в другом: зачем? Если написанное в лоб, за 10 минут, решение работает с адекватной скоростью, зачем мне тратить ценное время на предварительные оптимизации? Будет проблема, будем решать.
ЗЫ: Вы, конечно, можете сказать, что перерисовывать 4 инпута вместо одного — это фигня и нестрашно, но то же самое справедливо и для MobX — эту же форму можно нарисовать обычным реактом, не трогать (на этом уровне) MobX и не написать ни одного лишнего слова в коде.
Во-первых, именно так и скажу: если перерисовка 1000 компонентов <tr>...</tr> в таблице отрабатывает за ~18 мс, то перерисовка 4 полей тоже проблем не вызовет. Да хоть 20.
Во-вторых, конечно эту форму можно нарисовать на ванильном React, вообще без MobX. Но а) не за 10 минут, б) не декларативно, в) доступ к данным будет ручками. А ручками как раз очень не хочется, потому что больно, чревато багами и тестировать трудно.
Вообще давайте уже уточним, что я не продаю вам Statium как замену MobX в существующих проектах. Если у вас уже всё построено вокруг одного решения, то менять шило на мыло можно только, если причины очень весомые. Для существующих проектов они вряд ли найдутся.
А вот как замена ванильному useState и хукам Statium даёт очень заметные преимущества. Это примерно как если пересесть с телеги на болид Формулы-1. Вы попробуйте как-нибудь, просто для расширения горизонтов. Вдруг понравится. :)
Для меня, кстати сказать, опыт с RealWorld example был очень поучительным. Некоторые вещи оказались не такими удобными, как казалось при разработке библиотеки, но многое другое оказалось даже лучше, чем я предполагал. Надо ещё что-нибудь подобное замутить, dogfooding это всегда полезно...
Вот этот блок уже сам по себе уродство и адское нагромождение.
Я смотрю, наша дискуссия уже окончательно пала до вкусовщины? Можем не продолжать в таком случае, всё равно бессмысленно.
И это при условии что табличка то не из реальной жизни, у вас всего 3 колонки, в реальности их обычно 10 и больше.
Да сколько угодно колонок может быть. Какая разница, сколько их? Я ведь говорил, что это пример, написанный за 10 минут. В реальном коде никто не мешает декомпозировать, использовать HOC или хуки. Три варианта, на выбор.
Вы объединили тут 3 убогих подхода:
Тут даже не знаю, что ответить. Вы в курсе, что Render props являются вполне официально рекомендованным подходом? Про остальные пункты я даже не слышал, вы их только что придумали наверное.
Извините, но если вы в таком пренебрежительном тоне отметаете рекомендации разработчиков React, то либо потрудитесь предоставить веское обоснование, либо давайте закончим эту беседу.
В кратце так, mobx мутабельный и рекативный, вот почему) А React сам по себе имутабельный и не реактивный.
Т.е. теперь вы продаёте мне MobX под волшебным соусом якобы более высокой производительности? Ну нет, спасибо, такой перевод темы мне не интересен. Если вы уже забыли, дискуссия началась с того, что управление состоянием в приложении должно делаться просто и легко, потому что нет никаких весомых причин к обратному. Исследовать альтернативную вселенную псевдонаучного еслибдакабизма на тему потенциального выигрыша производительности мне недосуг.
Отлично, мы завели переменную в компоненте. Что это даёт? Ничего, всё равно надо подписываться на обновления вручную. Вот как в примере markelov69 ниже: создаём observable, скармливаем его в хук useState, и… Понимаем, что observable вообще не нужен, потому что хук сам по себе и решает вопросы хранения состояния и подписки на изменения этого состояния.
Я что-то упустил?
В ваших примерах вам state = {} написать не лень, а вот observable() написать — будет лень, так что ли?
Дело не в лени, а в совершенно различной семантике. Объект, который вы передаёте в initialState, выполняет несколько функций:
а) Задаёт список ключей с (потенциально) их типами
б) Присваивает ключам значения по умолчанию, которые абсолютно необходимы для первого рендера (React синхронный, не забывайте!)
в) Устанавливает ограничения на дальнейшие обновления состояния. Если вы не задали ключ в initialState, то установить его позже не получится, полетит исключение.
И всё это делает простое и очевидное присваивание полей в объекте. Который вы передаёте в модель, и тем самым уже подписываетесь на изменения этого состояния.
Это очень сильно отличается от простого создания экземпляра observable в переменную, не находите?
Конечно. Тут везде ровно одна механика — подписка на изменение, а как именно вы её выражаете — зависит от того, что вам нужно сделать.
Так вот я эту механику никак не вижу в вашем примере с MobX — если, конечно, не делать её вручную. В чём и состоит суть моей аргументации: зачем вообще заниматься этим вручную? Зачем дублировать в каждом компоненте императивный код, когда можно вместо этого декларировать состояние и его отношение к вашим компонентам? И всё это делается очень легко, очень быстро и наглядно, без лишней писанины.
Я понимаю, что вы работаете с MobX, он вам нравится и кажется естественным. Мне же MobX видится очень неловким натягиванием совы на глобус, по крайней мере в приложении к React. Слишком много лишних телодвижений нужно совершить для решения очень простых задач, а со сложными я даже связываться не стал.
В документации написано. В одно короткое предложение написано, кстати.
Это тоже нужно знать, учитывать, и тратить на чтение документации время. И потом на эсперименты с выяснением, а что же на самом деле происходит в вашем контексте с вашим кодом.
Если каждый мелкий аспект использования инструмента даёт высокую когнитивную нагрузку, то вы вместо решения своих задач начинаете всё время тратить на изучение инструмента. Это неэффективно.
Задавая вопрос в таком контексте — вы неизбежно придете к тому, что для формы с тремя полями вам не нужна ваша библиотека, вам не нужен MobX, и уж тем более вам не нужен реакт. Стейт-менеджмент вообще не имеет не малейшего смысла (любой), если вам «просто нужна форма с тремя полями». Стейт-менеджмент становится крайне полезен, когда у вас зоопарк разных контролов со сложной логикой взаимодействия.
Вот тут, как мне кажется, мы стали нащупывать суть вопроса. Почему вам кажется, что для формы с тремя полями не нужно управление состоянием? Это не пассивно-агрессивная атака, мне в самом деле интересно, почему вы так думаете. Честно.
Не потому ли, что в популярных библиотеках (Redux, MobX) управление состоянием делается настолько сложно, что временные и когнитивные затраты несопоставимы с результатом? И для простейшей формы с тремя полями проще не связываться, а сделать руками?
Обратите внимание на популярность хуков. Почему сразу после их появления многие разработчики стали использовать их вместо Redux или MobX? На мой взгляд, именно по причине простоты использования: useState не требует никаких церемоний, создания констант-экшнов-редьюсеров-и-т.д., или классов с @observable, и т.п.
К сожалению, у хуков очень много недостатков. Пресловутые Rules of Hooks, трудности с тестированием, трудности с доступом к состоянию из дочерних компонентов, потеря декларативности презентационных компонентов, масса ручной работы, потеря функциональной чистоты компонентов, случайные баги… Хуки useState и useReducer это не самый плохой компромисс, но всё же плохой.
А у Statium ни одного из этих недостатков нет, и использовать ViewModel очень просто. Настолько просто и очевидно, что вопрос стоит уже по-другому: а какова может быть причина не использовать state management?
Я не вполне понимаю ваш способ чтения, потому что в самом начале readme английским по белому явно написано, что mobx-react-lite может спокойно использоваться вместе с mobx-react.
Первый абзац из документации mobx-react-lite гласит, что:
Class based components are not supported except using <Observer> directly in class render method. If you want to transition existing projects from classes to hooks (as most of us do), you can use this package alongside the mobx-react just fine. The only conflict point is about the observer HOC. Subscribe to this issue for a proper migration guide.
Дальше я читать не стал, потому что я не использую MobX в своих проектах и не буду, потому что мне не нужны искуственные ограничения и проблемы, с ним связанные.
К тому же, если речь идёт о миграции с классов на хуки, то зачем вообще MobX? Я так до сих пор и не понимаю.
А если почитать еще чуть глубже нормальным способом чтения, то там опять же явно написано, что mobx-react-lite вполне применим в классовых компонентах.
Ещё раз, зачем мне читать глубже? Это вы предлагаете мне использовать MobX, потому что он гораздо лучше и проще моего решения. Я просто не понимаю, каким образом библиотека, которая фокусируется на других вещах (первый абзац из документации):
"MobX in React brings easy reactivity to your components. It can handle whole application state as well."
… (т.е. управление состоянием заявлено как побочный эффект реактивности), обладает багажом несовместимых изменений, требует глубокого изучения для решения моих простых насущных проблем, может быть лучшим вариантом.
Ещё раз: я понимаю, что лично вам MobX нравится и кажется лучшим решением. А лично мне он не кажется даже просто хорошим решением. Слишком много усложнений для чего? Easy reactivity? Так мне оно не нужно (что бы это ни было), мне нужно форму с тремя полями. Или таблицу с фильтрацией.
Раз вы такое пишете, значит вы вообще не копали MobX и даже не пробовали его в деле
Нет, в больших приложениях, построенных исключительно на MobX, не пробовал. Пробовал а) построить на нём мелкий проект, б) внедрить в большом проекте, где state management делался вручную на классовых компонентах и setState. В процессе копал достаточно (как мне показалось) глубоко, но скорее всего в какую-то другую сторону.
Вот, прям сходу на коленке, codesandbox.io/s/admiring-ishizaka-ncsfk
Брр, вы меня теперь уже совсем запутали. Если вы используете хук useState, то зачем вообще нужен MobX?
Нет, не то. Я не об этом говорю. Баги бывают, таймауты, переполнение базы, всё что угодно. Но это контролируемые условия. Есть проблема — вы её решаете, и всё работает взад. Баг можно пофиксить, базу можно подчистить или ещё ноду накатить, в сети QoS подвинтить или в чём там проблема была.
В front end нормой жизни является непредсказуемое и неконтролируемое изменение условий. Вот как с тем примером с Adblock. Вы на самом деле ничего не делали, правда! — а оно бац, и поломалось. Совсем. И не починить, потому что юзера не заставишь отказаться от Adblock, а домен не убрать из списка. Это не баг и не проблема с сетью. Это именно изменение ТЗ, на лету, постоянно.
Я же не пытаюсь выпендриться крутизной, я просто объясняю, что front end это очень особенная область в разработке софта. Тут царит хаос и непредсказуемость, которая требует выдержки и определённого склада характера. Если вам нравятся чётко поставленные задачи в CS стиле: ввод -> алгоритм -> вывод, а изменение ТЗ вызывает беспричинную раздражительность, то лучше во front end не заходить. Шишек набьёте, ЧСВ синяками покроется, и будете потом всем с умным видом рассказывать, какие они идиоты и как надо было всё делать вообще не так.
Зарплата была моим условием при заключении контракта, извините.
Так с какого перпугу я должен перед этими людьми бить чечётку?
В вашем контракте не написано, что вы работаете работу за зарплату? Или вам просто так платят, за красивые глаза? Вам ставят задачи, вы их решаете. Не будете решать — уволят, наймут другого.
Ну так если хотите — так и сделайте. Кто мешает? Вдавайтесь…
Это очепятка была. Не хочу я вдаваться в эти подробности.
с другой стороны — наблюдаю не работающий уже неделю в Firefox ESR 68.4 интерфейс перевода денег с карты на карты в Yandex.Money… ну и как это сочетается, извините?
Да легко: кто вообще пользуется этим дурацким Firefox? Полтора пользователя, которые никому не нужны?
То, что реально нужно для реализации функциональности большей части сайтов отлично отрендерит и IE6.
Ваша самоуверенность вполне типична для back end разработчика, который заглядывал во front end пару раз, не осилил, и теперь делает очень категоричные заявления о том, что реально нужно для большей части сайтов.
Нет, ну ей-богу, скучно. На том давайте и закончим, всего хорошего.
Это, кстати, тоже преимущество Statium: не нужно никаких церемоний, изменения архитектурных решений и т.п. Можно просто взять и попробовать его использовать для одной маленькой формы. А потом для двух. А потом для всего модуля. Или приложения. Или не использовать, а продолжать с Redux.
И взаимодействие что с Redux, что с MobX сделать очень просто. По-моему, это большой плюс. Не находите?
В редаксе элементарно, в мобх зависит от использованного "над-фреймворка", но обычно тоже просто, тут нужно либо странный общий ключ у общего родителя, либо какую-то систему сигнализации.
Навскидку, странный общий ключ у общего родителя и в Statium вполне сработает.
В общем же случае мне пока трудно представить, почему было бы затруднительно просто вынести какие-либо ключи в общего родителя, без странностей. Какая презентационному компоненту разница, в какой модели находится ключ? Они же все доступны сверху донизу одинаково, в подписчике вообще ничего менять не надо.
В принципе горизонтальная сигнализация вполне возможна, но API для неё в рамках React придумать будет нетривиально. Я подумаю, задача интересная. :)
Да запросто. У вас есть 50 записей чего-то + фильтр (ну или сортировка). И черт с самим фильтром, быстро работает, а вот сбрасывание shallow compare (ну или shouldComponentUpdate) — большая проблема. Нужны уже реселекторы всякие там, чтобы computed всегда возвращал одно и то же значение.
Набросал за 10 минут, попробовал — при отображении 1000 записей фильтрация отрабатывает с едва заметной задержкой. В профилировщике самая медленная функция вне React — это FaaC в теле компонента Table, отрабатывает за 1.8 мс собственного времени. Это подход влоб, без оптимизаций.
У меня, конечно, мощный лаптоп и условия не приближенные к боевым, но я как-то плохо представляю себе отображение 1000 записей на экране даже телефона.
Или я всё же как-то неправильно понял задачу?
Ах да, прошлый раз забыл: а что там по рефакторингу? Если я захотел переименовать user в profile — мне идти по всему коду и менять надпись? Селекторы как абстракция придуманы не просто так (в mobx это проще, потому что всегда прямая ссылка на поле, ts справится с переименованием).
Не совсем понятно, что вы имеете в виду под селекторами. Если меняется имя ключа в модели, то конечно же нужно менять привязки. Как вариант, можно использовать HOC:
В таком случае имя ключа надо будет менять только в одном месте.
На практике, так ли часто это нужно? В наших проектах TypeScript не используется, и вроде проблем особых нет. Не то, чтобы я был так уж активно против TypeScript, просто стараюсь исходить из прагматических соображений: есть проблема — решаю. Проблем с рефакторингом пока не было, тесты решают всё. :)
Нормализованных. Пример: у меня есть список всех лекций, а на панели слева список текущих лекций. Я переименовал одну лекцию — как панель слева узнает о том, что я это сделал?
Если у вас данные в нормальном виде, то самый простой подход — использовать вычисленные значения для презентации, с помощью формул: https://github.com/riptano/statium#computed-values. Я как раз такой подход использовал в примере выше. При этом ничто не мешает хранить нормальные данные в родительской модели, а формулы декларировать в дочерних, по необходимости.
Изменения в одном месте отражаются сразу во всем приложении. Только у меня таки "таблиц" в терминах БД 50+. И, как я сказал, если их хранить все в рутовом ViewModel, тогда он вырождается в редакс (только без тайм тревела, логирования, мидлвейров, саги).
Ну, не совсем вырождается в Redux, хотя такой вариант возможен. У вас ведь приложение состоит не только из списка лекций? Есть какие-то другие страницы: календарь, пользовательский профиль, профиль студента, наверняка куча всего. Если вы абсолютно всё состояние приложения храните в front end, то может быть и имеет смысл хранить все данные в глобальной модели. А может и нет; в Redux у вас особо выбора нет, а в Statium есть.
Что касается недостатков, то я не спорю, по фичам Statium очень далеко до Redux. Но и time travel, и logging вполне возможны, я планирую их добавить в какой-то момент. Просто инструментарий пока не в приоритете (дед лайн близко).
А Middleware делается через applyState и контроллеры.
Это жуткий недостаток для меня. И для всех, кто хоть раз пытался рефакторить более-менее крупный проект, полный этого чертового get.
Если для вас это недостаток, то можно не использовать. :) Глубокая выборка из структуры опциональна и сделана больше для удобства (пример). А так никто не мешает привязываться по ключу к структуре данных и ходить по ней явным образом.
С архитектурной точки зрения, при unmount запросы должны отбрасываться либо их результат игнорироваться. Как минимум, не должно мигать пол страницы. Впрочем, тут только rxjs молодцом, с сагой еще можно извернуться, остальные технологии требуют достаточно больших телодвижений и спасает, что обычно данные в дочерних компонентах никому не нужны и факт их загрузки никому не мешает.
Вот я как раз вчера думал на эту тему. И придумал вот что: можно отменять запросы через какое-нибудь новое API, но это чревато побочными эффектами. А что, если просто бросать исключение? Если родительская модель (или контроллер) отмонтирована, то попытка вызвать $get/$set/$dispatch бросит что-нибудь типа ComponentUnmountedError. А пользовательский код может это проверить и отреагировать, как надо — включая отмену сетевых запросов и т.п.
Как думаете? Исключения в обработчиках всё равно надо ловить, так что дополнительная когнитивная нагрузка минимальна.
Это приведет, со временем, к миграции всего в рутовый ViewModel. В лучшем случае, в рутовый для модуля, а не всего приложения.
Может привести, а может и нет. Всё зависит от того, какое у вас приложение, с чем работает, и как. Вы же исходите из своей специфики, а я, например, из своей. Для того типа приложений, с которыми я работаю, хранение глобального состояния в клиенте нетипично; чаще нужно хранить локальную копию состояния в клиенте, и обновлять её с сервера/в сервер при навигации и прочих действиях пользователя. Зачем тут глобальная модель, в которой всё-всё-всё лежит?
Вообще надо сказать, что с "толстыми" приложениями в web я отлично знаком — раньше работал над фреймворком Ext JS, который как раз для таких приложений больше использовался. И специфику хранения больших наборов нормализованных данных с моделированием отношений между ними я тоже знаю не понаслышке. :)
… опять все всплывает вверх.
Если всё всплывает вверх, то значит, это так нужно и логично. Не вижу противоречий. Просто исходя из моей практики, далеко не всё и не всегда всплывает вверх. Если локализовывать состояние в ViewModel, даже большое количество этого состояния, то получается такой мини-Redux для каждого модуля (страницы?). Ничего странного в этом нет.
А вот когда она нужна, локализация состояния делается в Statium очень легко и просто. А в Redux не очень, поэтому обычно все и говорят, что она не нужна. :)
Ну come on, это просто переменная. Переменную можно объявить примерно где угодно, и передать примерно куда угодно.
Вы с React работаете? Я не просто так спрашиваю, мне интересен MobX в контексте React. Вне этого контекста он мне не интересен.
А в React просто переменная мало чем пригодится. Если вы её в модуле объявите, будет у вас один экземпляр observable, созданный в момент загрузки модуля. А компонент рендерится гораздо позже, что мне толку от такой переменной? А если мне надо два экземпляра такого компонента?
Если у вас компонентный UI, то странно жаловаться, что надо, оказывается, эти самые компоненты делать, не?
Я жаловался не на то, что надо компоненты делать. А на то, что в компонентах React надо будет вручную делать observable и обвязку к нему. Что, на мой взгляд, совершенно не упрощает код даже в сложных случаях.
Вы же сами выше по ветке утверждали, что всё элементарно просто:
И действительно, зачем? Что вам мешает сделать простой const model = observable({ бла-бла }) и залить туда всё нужное?
Вот я и прошу уточнить, как именно этот подход использовать в React + MobX, чтобы было просто, наглядно и очевидно. Я с вами не спорю, а пытаюсь учиться. Очевидно, что моё MobX-fu не так сильно, как я думал.
Очевидно же, что MobX этими вопросами не занимается — это просто реактивная библиотека. Как хотите, так и перерисовывайте — в общем случае это просто объявление reaction() или даже autorun() внутри компонента, который при изменении нужных свойств у обсервабла запустит перерисовку компонента. Это, собственно, именно то, что делает observer из mobx-react. Никакой магии тут нет.
Если вам нужно это понимать — для этого и существует все эти reaction(), autorun(), или даже просто observe(). Обычная подписочная модель.
Это, по-вашему, подпадает под определение "простой в использовании библиотект"?
Например, чем отличается reaction() от autorun()? Как определить, в каком случае использовать то, а в каком другое? А если окажется, что я ошибся и случай не тот, а другой? Как менять одно на другое? Как тестировать, что при замене моя функциональность не сломалась?
Зачем мне нужно знать, что MobX не занимается вопросами состояния компонентов в React, а для этого нужен react-mobx? Мне, блин, форму с тремя полями надо сделать.
Я не знаю, как вы искали, потому что у меня поиск занял примерно 5 секунд — на то, чтоб набрать три слова и на то, чтоб гугл выдал результаты.
Спасибо, я не догадался добавить hooks в поиск. Я искал "react mobx no class", и первый результат на StackOverflow указывает на то, что MobX хуки не поддерживает.
Из ваших результатов видно, что уже (вроде бы?) поддерживает, но не сам MobX, а сторонняя библиотека mobx-react-lite, у которой есть конфликты с существующим кодом. Поддержка хуков планируется в MobX 6, но в официальном репозитории MobX нет упоминания версии 6.
А если почитать чуть глубже, то начинаются ограничения: если хуки, то классовые компоненты нельзя. А если мне надо componentWillUnmount? Тогда старый MobX с классами, а он в хуки не умеет. Приехали обратно.
Вот честно, я уже потратил ещё один час на более глубокое ознакомление с MobX, и этот час совершенно не приблизил меня к пониманию, каким образом React + MobX может быть проще в использовании, чем React + Statium.
Спасибо за критику! Нет, правда, мне очень интересны критические/негативные отзывы. Это возможность узнать о проблемах и быстрее их решить. :) Текущая версия библиотеки всего 0.2.3, ещё многое нужно решить! И API тоже можно менять, в разумных пределах. :)
Далее по пунктам (без номеров, markdown странно работает в цитатах):
Абстрагируясь от идеи вписывания бизнес-логики во вью
Мне кажется, тут скорее проблема с подачей материала (с моей стороны, буду править). Бизнес-логика вписывается не в презентационный слой, а в ViewController. Это сама по себе отдельная сущность, которая занимается только асинхронной логикой. <ViewModel controller={...} /> это просто сокращение, чтобы отдельно ViewController не описывать (и сэкономить на отступах! :), а вообще ничто не мешает описывать логику вообще отдельно от всего, например так:
import { ViewController } from 'statium';
const MyController = ({ children }) => (
<ViewController initialize={...} handlers={...} >
{ children }
</ViewController>
);
export default MyController;
… и использовать потом как изолированный компонент.
Стоит убрать FaaC (function as a child) в пользу хуков. Сильно сократит и упростит код. В разы.
Вы имеете в виду FaaC в компоненте Bind? Можно вместо него использовать хук useBindings:
Что интересно, я тоже думал, что хуки сократят и упростят код компонентов. На практике оказалось, что мне самому существенно быстрее и проще использовать <Bind> и withBindings, хотя хук тоже иногда пригождается.
Показал бы в примерах как это тестировать.
Обязательно! Тестирование — это одна из сильных фишек Statium. К сожалению, я не могу показать код, который мы для тестирования боевых приложений используем, но обязательно добавлю примеры тестирования в RealWorld приложение. Просто не успел ещё, я его в прошлые выходные запилил по-быстрому. :)
Как вызвать метод контроллера компонента сверху? Окей, диспатч или более странный способ через $set, допустим. К слову, в редаксе нельзя напрямую писать в store, никогда не задумывались почему?)
а) Вызвать метод родительского контроллера: $dispatch.
б) $set устанавливает значение в модели-владельце. Это не обязательно родительская ViewModel. $set карабкается по дереву, находит ближайшего владельца и устанавливает значение. Если какая-то из дочерних моделей переназначает ключ, то обновляться будет она (из низлежащих). Надо будет добавить инструментарий для отслеживания таких ситуаций.
в) В Statium тоже нельзя напрямую писать в данные ViewModel, только через функцию-setter. При этом можно либо валидировать значения перед записью (applyState), либо полностью предотвращать запись и делать что-то ручное (protectedKeys + ViewController event handler). По умолчанию модель просто обновляет значение, потому что это самый частый случай.
Как среагировать на изменение состояния соседа? А что если сейчас вам не нужно связывать две панели между собой и вы создали достаточно объемную ViewModel в каждом из них, а потом вдруг понадобилось их связать (они стали взаимно зависимыми)?
Объединить оба компонента под родительской ViewModel и вынести связанные ключи в неё. Это безболезненно и очень быстро. :) И в смысле разработки, и в смысле производительности.
Это одна из самых важных фич ViewModel: обновления состояния всегда ограничены минимально возможным регионом. Автоматически.
А чтение из модели по простому ключу работает очень, очень быстро. Потому что внутри модели данные хранятся в объекте, связанном по прототипной цепочке с такими же хранилищами в родительских моделях. Я плохо представляю себе, насколько объемной должна быть модель, чтобы выборка данных стала бутылочным горлышком. Проблемы начнутся гораздо раньше и в других местах...
У меня есть список пользователей. Каждый раз я открываю пользователя — я должен его догрузить. Но у меня еще есть N методов для каждого пользователя. Как вы это организуете в своем фреймворке? Каждый пользователь — отдельный ViewModel? А как тогда будете кешировать пользователей?
Самый очевидный вариант — да, рендерить ViewModel на каждого пользователя. Я в примере так и сделал со списком статей, проблем с производительностью не заметил при N = 100. N = 500 ломает Chrome, но это почему-то происходит и без ViewModel на странице, я копать не стал (времени не было). Т.е. при рендеринге 100 ViewModel при профилировке этих вызовов вообще не заметно в Call Trace.
Про кеширование не совсем понял, уточните?
Продолжение 5-го пункта. У меня в реальном проекте более 50 доменных объектов (это мы еще плохо нормализовали данные). И они, к сожалению, достаточно сильно связаны между собой. У вас вырадится в редакс? Или будет иерархия из 50-ти вложенных контекстов?
Тоже не совсем понял. 50 вложенных объектов, или просто связанных? В ViewModel можно хранить объекты, и вообще любые структуры данных. Селекторами можно лезть глубоко в структуру, как для чтения, так и для записи:
Никакой магии тут нету, кстати сказать, банальные lodash.get/set. Ну, и чуточку обвязки.
Как "прервать" / отбросить / отменить асинхронный метод? Что если я уже успел покинуть viewModel, которое еще не успело что-то загрузить, при этом оно перепишет какое-то значение родителя?
На текущий момент ничего страшного не случится, просто в консоль будут сыпаться надоедливые нотации React о том, что нельзя обновлять состояние демонтированного компонента. Как эту проблему решить, пока думаю, вариант с исключениями вполне подходит, но душа просит чего-то более элегантного.
С архитектурной точки зрения мне кажется, что таких штук допускать не надо. Данные надо держать близко к коду. На практике, конечно, нужно с этим что-то делать. Идеи приветствуются. :)
Да, а вот вариант с перезаписыванием значения в родительской ViewModel мне в голову пока не приходил, спасибо. Т.е. в случае:
… если event уже выстрелил, а потом Child демонтируется, то… хм… Надо тест написать, я не уверен, что произойдёт в текущем коде. Скорее всего значение foo изменится на новое, без ошибок.
К слову, как в методе контроллера получить значение чужой viewModel?
Если это родительская модель, то просто $get('foo') — все ключи вышестоящих моделей доступны детям (но не наоборот). Если это не родственная модель, то никак. Это фича. Выносите ключи в общего родителя.
Самое главное: зачем это все, если есть useReducer и useContext, которые позволяют получить все тоже самое? Ну вместо $set будет у вас dispatch({type: 'set'}), обертку можно легко сделать.
Так Statium и есть эта самая обёртка, чтобы вручную в каждом компоненте не писать. :) Точнее, первоначальная версия ViewModel была такой обёрткой, 100 строк кода с useContext и useReducer. А зачем? Чтобы управление состоянием вынести за скобки компонента и перестать им заниматься. И думать о нём. Вообще.
Кроме этого: иерархический доступ к моделям. Этого вручную на хуках по-быстрому не сделать.
А ещё для метапрограммирования. Чтобы иметь компонент для динамического определения формата состояния компонентов, и обработки этого состояния. Одно из боевых применений в наших приложениях — компонент Report, который принимает объект с определением отчёта, полей, связей между ними, состояния таблиц, и т.д.; создаёт ViewModel и рендерит все дочерние компоненты. Очень удобно, когда встроенных отчётов 50+ в разных форматах, и нужно периодически добавлять новые.
ViewController рождён в битве с back end и выкован в схватках с Apollo GraphQL. В какой-то момент back end сказало: ква, быстрее не можем. Грузите данные в tree grid порциями: сперва верхний ряд строк, потом догружайте по необходимости. Попытки выкрутить хуки GraphQL и сделать такую ступенчатую загрузку с промежуточными состояниями и маскировкой таблицы в синхронном коде довели меня до белого каления, я плюнул и добавил к ViewModel ещё и ViewController. А потом добился разрешения всё это выложить в open source. :)
Это RealWorld приложение я слабал на коленке в прошлые выходные. Сам код Statium в тестировании уже более полугода, полёт номинальный. Скоро релиз. :)
Потому, что когда вырастаешь в юниора, то начинаешь понимать, что TypeScript — это костыли для стажёров, а для вырастания в ведущие надо уже понимать, что плыть против течения себе дороже. Все побежали, и я побежал. ;)
Может. Но это сторонние для вас сервисы. Этот замусоленный уже до дыр пример с Adblock был о другом: представьте, что у вас в своём облаке, своих контейнерах, своих сборках и своей JVM всё открыто нараспашку и можно внедрить любой сторонний код. Ну т.е. вообще любой, и в любой момент.
Ну вот именно об этом я и талдычу уже который день: вы контролируете эти условия. Вы можете реагировать на внешние события и нейтрализовать их в своём коде или ещё как-нибудь. Что-то сломалось? Починили. Ненадёжный источник данных? Обложили эвристиками, проверками и перепроверками. Это контроль над средой. Не идеальный, но контроль.
А у нас иногда проблему можно решить, а иногда нет. Вот никак. В гугле правила SEO тихо поменялись, и всё, вашему стартапу пришёл он самый, белый и пушистый. Лично знаю людей, которые на этом потеряли миллионы зелёных денег, и повезло, что не весь бизнес.
Эту старую песню о главном можно продолжать очень долго, но front end это не только визуализация данных.
Вообще говоря, эта дискуссия ни о чём, и ни к каким выводам не приведёт, а обед давно уже остыл. Честно, давайте правда уже закончим. Всего хорошего.
Да ну ладно, о чём вы в самом деле? О какой анонимности тут речь идёт, когда товарищ авторитарно так заявляет, что его нужно слушаться ну вот нипочему? Мне просто любопытно, что это за птиц такой, всезнающий, всеумеющий и в истину в последней инстанции вхожий. А то чем чёрт не шутит, соберёшься этак собеседоваться в $куда-нибудь, а там как раз слушаться надо. А пацаны-то и не знали.
Что касается профиля на этом сайте, то мне он не настолько важен, чтобы время тратить. Какое там качество аргументации, хабр это наискось за обедом полистать и языком почесать между делом, пока код в голове варится. А если вам интересен мой профиль, то в гугле вторая ссылка на мой полудохлый бложек, а четвёртая на Github. В отличие от, кстати.
Вы бы своё реальное имя в профиль добавили, что ли. И название копании, в которой вас нужно слушаться.
Страна должна знать своих героев. :)
Бывает, конечно бывает. И так бывает, и сяк бывает, хаос и энтропия никого не оставят в покое. Просто я всё время наблюдаю со стороны, как жизнь идёт у наших back end/platform/devops товарищей, которые всё время ужа с ежом скрещивают… Спокойнее у них там, гораздо. :)
Иногда даже подумываю, а не плюнуть ли на всё, натянуть Golang какой-нибудь на голову и податься через забор. Но скучно там всё же, когда привык к адреналину, размеренная жизнь как-то не цепляет уже. Да и плюшки с этой стороны вкуснее. :)
В таком случае я не вижу, как это противоречит заявлению "front end это хаос". Даже более: поздравляю, у вас тоже хаос. :))
Дался вам этот adblock, это просто один пример был. :) Мысль-то была очень простая: в front end разработке настолько часто встречаются неконтролируемые изменения условий, приводящие к кардинальным изменениям в софте и процессах, что это является нормой вещей. Делали приложение для десктопа и современных браузеров? Поздравляю, мы вчера подписали контракт на $1005000 с XYZ и им надо IE11. Упс, а Chrome пять минут назад обновился и теперь скроллинг не работает, срочно фиксить. А что, у вас сайт на телефонах не работает? Какая разница, что в ТЗ было, это вчера было! Что? Гугл поднял расценки на внедрённые карты, а у нас на них весь интерфейс завязан?
Ну и т.п. Куда уж тут до чуточку недопереоптимизированных алгоритмов, выкатить бы что-нибудь. :)
Я вам даже больше скажу: так вообще везде. Помнится, в начале 2000х, когда ВТБ по случаю прикупил себе Гута-банк, мне пришлось разворачивать колл-центр ВТБ24 с оборудования Гуты, но на площадке ВТБ. Сроки сжатые, нужное количество E1 пробросить не успевали, поэтому можно было только Voice-over-IP, новинку по тем временам. За неделю прошли все инстанции (в ВТБ! представляете?!), всё подписали, всё согласовали — стыковали сети, пограничку настроили, всё работает. Ещё за неделю телефоны развернули, сценарии прописали, всё готово к сдаче.
А потом накануне запуска безопасники ВТБ такие: а мы тут вот затылок почесали, короче, не нравится нам RTP этот ваш, небезопасный он. Вы там что-нибудь придумывайте, а мы вам порты на файрволле зарубили. Всего хорошего.
Вот 15 лет назад было, а до сих пор помню. Потому что такое резкое и несовместимое с разумом изменение внешних условий всё же не такое частое явление… Вне front end. :)
Да ладно, ну что вы в самом деле. Проблема же не в разнородности проблем, а в их количестве. Как кто-то когда-то сказал, Quantity is a quality of its own. :)
В общем, не хочу показаться невежливым, но мне кажется, что дискуссия себя исчерпала. Давайте на том и закончим, всего хорошего.
Так они и обязаны перерисовываться, потому что зависят от состояния ключей
filter
,page
иlimit
. Напрямую зависят. Я не вижу, в чём здесь проблема.Можно и утащить, если есть необходимость. Можно добавить кучу оптимизаций, чтобы не пересчитывать
values
каждый раз, мемоизировать формулу, например. Вопрос в другом: зачем? Если написанное в лоб, за 10 минут, решение работает с адекватной скоростью, зачем мне тратить ценное время на предварительные оптимизации? Будет проблема, будем решать.Во-первых, именно так и скажу: если перерисовка 1000 компонентов
<tr>...</tr>
в таблице отрабатывает за ~18 мс, то перерисовка 4 полей тоже проблем не вызовет. Да хоть 20.Во-вторых, конечно эту форму можно нарисовать на ванильном React, вообще без MobX. Но а) не за 10 минут, б) не декларативно, в) доступ к данным будет ручками. А ручками как раз очень не хочется, потому что больно, чревато багами и тестировать трудно.
Вообще давайте уже уточним, что я не продаю вам Statium как замену MobX в существующих проектах. Если у вас уже всё построено вокруг одного решения, то менять шило на мыло можно только, если причины очень весомые. Для существующих проектов они вряд ли найдутся.
А вот как замена ванильному
useState
и хукам Statium даёт очень заметные преимущества. Это примерно как если пересесть с телеги на болид Формулы-1. Вы попробуйте как-нибудь, просто для расширения горизонтов. Вдруг понравится. :)Для меня, кстати сказать, опыт с RealWorld example был очень поучительным. Некоторые вещи оказались не такими удобными, как казалось при разработке библиотеки, но многое другое оказалось даже лучше, чем я предполагал. Надо ещё что-нибудь подобное замутить, dogfooding это всегда полезно...
Я смотрю, наша дискуссия уже окончательно пала до вкусовщины? Можем не продолжать в таком случае, всё равно бессмысленно.
Да сколько угодно колонок может быть. Какая разница, сколько их? Я ведь говорил, что это пример, написанный за 10 минут. В реальном коде никто не мешает декомпозировать, использовать HOC или хуки. Три варианта, на выбор.
Тут даже не знаю, что ответить. Вы в курсе, что Render props являются вполне официально рекомендованным подходом? Про остальные пункты я даже не слышал, вы их только что придумали наверное.
Извините, но если вы в таком пренебрежительном тоне отметаете рекомендации разработчиков React, то либо потрудитесь предоставить веское обоснование, либо давайте закончим эту беседу.
Т.е. теперь вы продаёте мне MobX под волшебным соусом якобы более высокой производительности? Ну нет, спасибо, такой перевод темы мне не интересен. Если вы уже забыли, дискуссия началась с того, что управление состоянием в приложении должно делаться просто и легко, потому что нет никаких весомых причин к обратному. Исследовать альтернативную вселенную псевдонаучного еслибдакабизма на тему потенциального выигрыша производительности мне недосуг.
Просто потому, что я в эту тему копал очень глубоко (нет, не в MobX, в прозводительность), и самый главный урок оказался простым: premature optimization is the root of all evil. ©
Вот смотрите, таблица с асинхронной загрузкой записей, фильтрацией и страницами: https://codesandbox.io/s/elated-buck-29fgg
За 10 минут, да. Где адские нагромождения-то? :)
Woot! А я и в CodeSandbox, оказывается, могу: https://codesandbox.io/s/elated-buck-29fgg
Да я-то ваш код смотрел. Это вам я никак не могу донести свою мысль, видимо плохо объясняю.
Давайте попробуем вот так: https://codesandbox.io/s/xenodochial-snyder-hngmw
Так зачем MobX-то? :)
Отлично, мы завели переменную в компоненте. Что это даёт? Ничего, всё равно надо подписываться на обновления вручную. Вот как в примере markelov69 ниже: создаём
observable
, скармливаем его в хукuseState
, и… Понимаем, чтоobservable
вообще не нужен, потому что хук сам по себе и решает вопросы хранения состояния и подписки на изменения этого состояния.Я что-то упустил?
Дело не в лени, а в совершенно различной семантике. Объект, который вы передаёте в
initialState
, выполняет несколько функций:а) Задаёт список ключей с (потенциально) их типами
б) Присваивает ключам значения по умолчанию, которые абсолютно необходимы для первого рендера (React синхронный, не забывайте!)
в) Устанавливает ограничения на дальнейшие обновления состояния. Если вы не задали ключ в
initialState
, то установить его позже не получится, полетит исключение.И всё это делает простое и очевидное присваивание полей в объекте. Который вы передаёте в модель, и тем самым уже подписываетесь на изменения этого состояния.
Это очень сильно отличается от простого создания экземпляра
observable
в переменную, не находите?Так вот я эту механику никак не вижу в вашем примере с MobX — если, конечно, не делать её вручную. В чём и состоит суть моей аргументации: зачем вообще заниматься этим вручную? Зачем дублировать в каждом компоненте императивный код, когда можно вместо этого декларировать состояние и его отношение к вашим компонентам? И всё это делается очень легко, очень быстро и наглядно, без лишней писанины.
Я понимаю, что вы работаете с MobX, он вам нравится и кажется естественным. Мне же MobX видится очень неловким натягиванием совы на глобус, по крайней мере в приложении к React. Слишком много лишних телодвижений нужно совершить для решения очень простых задач, а со сложными я даже связываться не стал.
Это тоже нужно знать, учитывать, и тратить на чтение документации время. И потом на эсперименты с выяснением, а что же на самом деле происходит в вашем контексте с вашим кодом.
Если каждый мелкий аспект использования инструмента даёт высокую когнитивную нагрузку, то вы вместо решения своих задач начинаете всё время тратить на изучение инструмента. Это неэффективно.
Вот тут, как мне кажется, мы стали нащупывать суть вопроса. Почему вам кажется, что для формы с тремя полями не нужно управление состоянием? Это не пассивно-агрессивная атака, мне в самом деле интересно, почему вы так думаете. Честно.
Не потому ли, что в популярных библиотеках (Redux, MobX) управление состоянием делается настолько сложно, что временные и когнитивные затраты несопоставимы с результатом? И для простейшей формы с тремя полями проще не связываться, а сделать руками?
Обратите внимание на популярность хуков. Почему сразу после их появления многие разработчики стали использовать их вместо Redux или MobX? На мой взгляд, именно по причине простоты использования:
useState
не требует никаких церемоний, создания констант-экшнов-редьюсеров-и-т.д., или классов с@observable
, и т.п.К сожалению, у хуков очень много недостатков. Пресловутые Rules of Hooks, трудности с тестированием, трудности с доступом к состоянию из дочерних компонентов, потеря декларативности презентационных компонентов, масса ручной работы, потеря функциональной чистоты компонентов, случайные баги… Хуки
useState
иuseReducer
это не самый плохой компромисс, но всё же плохой.А у Statium ни одного из этих недостатков нет, и использовать ViewModel очень просто. Настолько просто и очевидно, что вопрос стоит уже по-другому: а какова может быть причина не использовать state management?
Тем более такой, который позволяет совершенно безболезненно и за 10 минут масштабироваться от формы с тремя полями до таблицы с фильтрацией записей в реальном времени, например: https://gist.github.com/nohuhu/01a7bb42b1c1e1378ea0df6829799055
Первый абзац из документации mobx-react-lite гласит, что:
Дальше я читать не стал, потому что я не использую MobX в своих проектах и не буду, потому что мне не нужны искуственные ограничения и проблемы, с ним связанные.
К тому же, если речь идёт о миграции с классов на хуки, то зачем вообще MobX? Я так до сих пор и не понимаю.
Ещё раз, зачем мне читать глубже? Это вы предлагаете мне использовать MobX, потому что он гораздо лучше и проще моего решения. Я просто не понимаю, каким образом библиотека, которая фокусируется на других вещах (первый абзац из документации):
… (т.е. управление состоянием заявлено как побочный эффект реактивности), обладает багажом несовместимых изменений, требует глубокого изучения для решения моих простых насущных проблем, может быть лучшим вариантом.
Ещё раз: я понимаю, что лично вам MobX нравится и кажется лучшим решением. А лично мне он не кажется даже просто хорошим решением. Слишком много усложнений для чего? Easy reactivity? Так мне оно не нужно (что бы это ни было), мне нужно форму с тремя полями. Или таблицу с фильтрацией.
Нет, в больших приложениях, построенных исключительно на MobX, не пробовал. Пробовал а) построить на нём мелкий проект, б) внедрить в большом проекте, где state management делался вручную на классовых компонентах и setState. В процессе копал достаточно (как мне показалось) глубоко, но скорее всего в какую-то другую сторону.
Брр, вы меня теперь уже совсем запутали. Если вы используете хук
useState
, то зачем вообще нужен MobX?Нет, не то. Я не об этом говорю. Баги бывают, таймауты, переполнение базы, всё что угодно. Но это контролируемые условия. Есть проблема — вы её решаете, и всё работает взад. Баг можно пофиксить, базу можно подчистить или ещё ноду накатить, в сети QoS подвинтить или в чём там проблема была.
В front end нормой жизни является непредсказуемое и неконтролируемое изменение условий. Вот как с тем примером с Adblock. Вы на самом деле ничего не делали, правда! — а оно бац, и поломалось. Совсем. И не починить, потому что юзера не заставишь отказаться от Adblock, а домен не убрать из списка. Это не баг и не проблема с сетью. Это именно изменение ТЗ, на лету, постоянно.
Я же не пытаюсь выпендриться крутизной, я просто объясняю, что front end это очень особенная область в разработке софта. Тут царит хаос и непредсказуемость, которая требует выдержки и определённого склада характера. Если вам нравятся чётко поставленные задачи в CS стиле: ввод -> алгоритм -> вывод, а изменение ТЗ вызывает беспричинную раздражительность, то лучше во front end не заходить. Шишек набьёте, ЧСВ синяками покроется, и будете потом всем с умным видом рассказывать, какие они идиоты и как надо было всё делать вообще не так.
В вашем контракте не написано, что вы работаете работу за зарплату? Или вам просто так платят, за красивые глаза? Вам ставят задачи, вы их решаете. Не будете решать — уволят, наймут другого.
Это очепятка была. Не хочу я вдаваться в эти подробности.
Да легко: кто вообще пользуется этим дурацким Firefox? Полтора пользователя, которые никому не нужны?
Ваша самоуверенность вполне типична для back end разработчика, который заглядывал во front end пару раз, не осилил, и теперь делает очень категоричные заявления о том, что реально нужно для большей части сайтов.
Нет, ну ей-богу, скучно. На том давайте и закончим, всего хорошего.
Это, кстати, тоже преимущество Statium: не нужно никаких церемоний, изменения архитектурных решений и т.п. Можно просто взять и попробовать его использовать для одной маленькой формы. А потом для двух. А потом для всего модуля. Или приложения. Или не использовать, а продолжать с Redux.
И взаимодействие что с Redux, что с MobX сделать очень просто. По-моему, это большой плюс. Не находите?
Навскидку, странный общий ключ у общего родителя и в Statium вполне сработает.
В общем же случае мне пока трудно представить, почему было бы затруднительно просто вынести какие-либо ключи в общего родителя, без странностей. Какая презентационному компоненту разница, в какой модели находится ключ? Они же все доступны сверху донизу одинаково, в подписчике вообще ничего менять не надо.
В принципе горизонтальная сигнализация вполне возможна, но API для неё в рамках React придумать будет нетривиально. Я подумаю, задача интересная. :)
Не уверен, что до конца понял постановку проблемы, но что-то подобное должно работать вполне быстро: https://gist.github.com/nohuhu/01a7bb42b1c1e1378ea0df6829799055
Набросал за 10 минут, попробовал — при отображении 1000 записей фильтрация отрабатывает с едва заметной задержкой. В профилировщике самая медленная функция вне React — это FaaC в теле компонента Table, отрабатывает за 1.8 мс собственного времени. Это подход влоб, без оптимизаций.
У меня, конечно, мощный лаптоп и условия не приближенные к боевым, но я как-то плохо представляю себе отображение 1000 записей на экране даже телефона.
Или я всё же как-то неправильно понял задачу?
Не совсем понятно, что вы имеете в виду под селекторами. Если меняется имя ключа в модели, то конечно же нужно менять привязки. Как вариант, можно использовать HOC:
В таком случае имя ключа надо будет менять только в одном месте.
На практике, так ли часто это нужно? В наших проектах TypeScript не используется, и вроде проблем особых нет. Не то, чтобы я был так уж активно против TypeScript, просто стараюсь исходить из прагматических соображений: есть проблема — решаю. Проблем с рефакторингом пока не было, тесты решают всё. :)
Если у вас данные в нормальном виде, то самый простой подход — использовать вычисленные значения для презентации, с помощью формул: https://github.com/riptano/statium#computed-values. Я как раз такой подход использовал в примере выше. При этом ничто не мешает хранить нормальные данные в родительской модели, а формулы декларировать в дочерних, по необходимости.
Ну, не совсем вырождается в Redux, хотя такой вариант возможен. У вас ведь приложение состоит не только из списка лекций? Есть какие-то другие страницы: календарь, пользовательский профиль, профиль студента, наверняка куча всего. Если вы абсолютно всё состояние приложения храните в front end, то может быть и имеет смысл хранить все данные в глобальной модели. А может и нет; в Redux у вас особо выбора нет, а в Statium есть.
Что касается недостатков, то я не спорю, по фичам Statium очень далеко до Redux. Но и time travel, и logging вполне возможны, я планирую их добавить в какой-то момент. Просто инструментарий пока не в приоритете (дед лайн близко).
А Middleware делается через applyState и контроллеры.
Если для вас это недостаток, то можно не использовать. :) Глубокая выборка из структуры опциональна и сделана больше для удобства (пример). А так никто не мешает привязываться по ключу к структуре данных и ходить по ней явным образом.
Вот я как раз вчера думал на эту тему. И придумал вот что: можно отменять запросы через какое-нибудь новое API, но это чревато побочными эффектами. А что, если просто бросать исключение? Если родительская модель (или контроллер) отмонтирована, то попытка вызвать $get/$set/$dispatch бросит что-нибудь типа ComponentUnmountedError. А пользовательский код может это проверить и отреагировать, как надо — включая отмену сетевых запросов и т.п.
Как думаете? Исключения в обработчиках всё равно надо ловить, так что дополнительная когнитивная нагрузка минимальна.
Может привести, а может и нет. Всё зависит от того, какое у вас приложение, с чем работает, и как. Вы же исходите из своей специфики, а я, например, из своей. Для того типа приложений, с которыми я работаю, хранение глобального состояния в клиенте нетипично; чаще нужно хранить локальную копию состояния в клиенте, и обновлять её с сервера/в сервер при навигации и прочих действиях пользователя. Зачем тут глобальная модель, в которой всё-всё-всё лежит?
Вообще надо сказать, что с "толстыми" приложениями в web я отлично знаком — раньше работал над фреймворком Ext JS, который как раз для таких приложений больше использовался. И специфику хранения больших наборов нормализованных данных с моделированием отношений между ними я тоже знаю не понаслышке. :)
Если всё всплывает вверх, то значит, это так нужно и логично. Не вижу противоречий. Просто исходя из моей практики, далеко не всё и не всегда всплывает вверх. Если локализовывать состояние в ViewModel, даже большое количество этого состояния, то получается такой мини-Redux для каждого модуля (страницы?). Ничего странного в этом нет.
А вот когда она нужна, локализация состояния делается в Statium очень легко и просто. А в Redux не очень, поэтому обычно все и говорят, что она не нужна. :)
Вы с React работаете? Я не просто так спрашиваю, мне интересен MobX в контексте React. Вне этого контекста он мне не интересен.
А в React просто переменная мало чем пригодится. Если вы её в модуле объявите, будет у вас один экземпляр observable, созданный в момент загрузки модуля. А компонент рендерится гораздо позже, что мне толку от такой переменной? А если мне надо два экземпляра такого компонента?
Я жаловался не на то, что надо компоненты делать. А на то, что в компонентах React надо будет вручную делать observable и обвязку к нему. Что, на мой взгляд, совершенно не упрощает код даже в сложных случаях.
Вы же сами выше по ветке утверждали, что всё элементарно просто:
Вот я и прошу уточнить, как именно этот подход использовать в React + MobX, чтобы было просто, наглядно и очевидно. Я с вами не спорю, а пытаюсь учиться. Очевидно, что моё MobX-fu не так сильно, как я думал.
Это, по-вашему, подпадает под определение "простой в использовании библиотект"?
Например, чем отличается reaction() от autorun()? Как определить, в каком случае использовать то, а в каком другое? А если окажется, что я ошибся и случай не тот, а другой? Как менять одно на другое? Как тестировать, что при замене моя функциональность не сломалась?
Зачем мне нужно знать, что MobX не занимается вопросами состояния компонентов в React, а для этого нужен react-mobx? Мне, блин, форму с тремя полями надо сделать.
Спасибо, я не догадался добавить hooks в поиск. Я искал "react mobx no class", и первый результат на StackOverflow указывает на то, что MobX хуки не поддерживает.
Из ваших результатов видно, что уже (вроде бы?) поддерживает, но не сам MobX, а сторонняя библиотека mobx-react-lite, у которой есть конфликты с существующим кодом. Поддержка хуков планируется в MobX 6, но в официальном репозитории MobX нет упоминания версии 6.
А если почитать чуть глубже, то начинаются ограничения: если хуки, то классовые компоненты нельзя. А если мне надо
componentWillUnmount
? Тогда старый MobX с классами, а он в хуки не умеет. Приехали обратно.Вот честно, я уже потратил ещё один час на более глубокое ознакомление с MobX, и этот час совершенно не приблизил меня к пониманию, каким образом React + MobX может быть проще в использовании, чем React + Statium.
А форма с тремя полями? Засекайте время:
… 3 минуты, это я опечатываюсь много.
Спасибо за критику! Нет, правда, мне очень интересны критические/негативные отзывы. Это возможность узнать о проблемах и быстрее их решить. :) Текущая версия библиотеки всего 0.2.3, ещё многое нужно решить! И API тоже можно менять, в разумных пределах. :)
Далее по пунктам (без номеров, markdown странно работает в цитатах):
Мне кажется, тут скорее проблема с подачей материала (с моей стороны, буду править). Бизнес-логика вписывается не в презентационный слой, а в ViewController. Это сама по себе отдельная сущность, которая занимается только асинхронной логикой.
<ViewModel controller={...} />
это просто сокращение, чтобы отдельно ViewController не описывать (и сэкономить на отступах! :), а вообще ничто не мешает описывать логику вообще отдельно от всего, например так:… и использовать потом как изолированный компонент.
Вы имеете в виду FaaC в компоненте Bind? Можно вместо него использовать хук
useBindings
:Что интересно, я тоже думал, что хуки сократят и упростят код компонентов. На практике оказалось, что мне самому существенно быстрее и проще использовать
<Bind>
иwithBindings
, хотя хук тоже иногда пригождается.Обязательно! Тестирование — это одна из сильных фишек Statium. К сожалению, я не могу показать код, который мы для тестирования боевых приложений используем, но обязательно добавлю примеры тестирования в RealWorld приложение. Просто не успел ещё, я его в прошлые выходные запилил по-быстрому. :)
а) Вызвать метод родительского контроллера: $dispatch.
б) $set устанавливает значение в модели-владельце. Это не обязательно родительская ViewModel. $set карабкается по дереву, находит ближайшего владельца и устанавливает значение. Если какая-то из дочерних моделей переназначает ключ, то обновляться будет она (из низлежащих). Надо будет добавить инструментарий для отслеживания таких ситуаций.
в) В Statium тоже нельзя напрямую писать в данные ViewModel, только через функцию-setter. При этом можно либо валидировать значения перед записью (applyState), либо полностью предотвращать запись и делать что-то ручное (protectedKeys + ViewController event handler). По умолчанию модель просто обновляет значение, потому что это самый частый случай.
Объединить оба компонента под родительской ViewModel и вынести связанные ключи в неё. Это безболезненно и очень быстро. :) И в смысле разработки, и в смысле производительности.
Это одна из самых важных фич ViewModel: обновления состояния всегда ограничены минимально возможным регионом. Автоматически.
А чтение из модели по простому ключу работает очень, очень быстро. Потому что внутри модели данные хранятся в объекте, связанном по прототипной цепочке с такими же хранилищами в родительских моделях. Я плохо представляю себе, насколько объемной должна быть модель, чтобы выборка данных стала бутылочным горлышком. Проблемы начнутся гораздо раньше и в других местах...
Самый очевидный вариант — да, рендерить ViewModel на каждого пользователя. Я в примере так и сделал со списком статей, проблем с производительностью не заметил при N = 100. N = 500 ломает Chrome, но это почему-то происходит и без ViewModel на странице, я копать не стал (времени не было). Т.е. при рендеринге 100 ViewModel при профилировке этих вызовов вообще не заметно в Call Trace.
Про кеширование не совсем понял, уточните?
Тоже не совсем понял. 50 вложенных объектов, или просто связанных? В ViewModel можно хранить объекты, и вообще любые структуры данных. Селекторами можно лезть глубоко в структуру, как для чтения, так и для записи:
Никакой магии тут нету, кстати сказать, банальные
lodash.get/set
. Ну, и чуточку обвязки.Вы мысли мои читаете? :)) Только час назад открыл этот тикет: https://github.com/riptano/statium/issues/13.
На текущий момент ничего страшного не случится, просто в консоль будут сыпаться надоедливые нотации React о том, что нельзя обновлять состояние демонтированного компонента. Как эту проблему решить, пока думаю, вариант с исключениями вполне подходит, но душа просит чего-то более элегантного.
С архитектурной точки зрения мне кажется, что таких штук допускать не надо. Данные надо держать близко к коду. На практике, конечно, нужно с этим что-то делать. Идеи приветствуются. :)
Да, а вот вариант с перезаписыванием значения в родительской ViewModel мне в голову пока не приходил, спасибо. Т.е. в случае:
… если
event
уже выстрелил, а потом Child демонтируется, то… хм… Надо тест написать, я не уверен, что произойдёт в текущем коде. Скорее всего значение foo изменится на новое, без ошибок.Если это родительская модель, то просто $get('foo') — все ключи вышестоящих моделей доступны детям (но не наоборот). Если это не родственная модель, то никак. Это фича. Выносите ключи в общего родителя.
Так Statium и есть эта самая обёртка, чтобы вручную в каждом компоненте не писать. :) Точнее, первоначальная версия ViewModel была такой обёрткой, 100 строк кода с
useContext
иuseReducer
. А зачем? Чтобы управление состоянием вынести за скобки компонента и перестать им заниматься. И думать о нём. Вообще.Кроме этого: иерархический доступ к моделям. Этого вручную на хуках по-быстрому не сделать.
Или вот для синхронизации состояния с URL, легко и непринуждённо: https://github.com/riptano/urlito.
А ещё для метапрограммирования. Чтобы иметь компонент для динамического определения формата состояния компонентов, и обработки этого состояния. Одно из боевых применений в наших приложениях — компонент Report, который принимает объект с определением отчёта, полей, связей между ними, состояния таблиц, и т.д.; создаёт ViewModel и рендерит все дочерние компоненты. Очень удобно, когда встроенных отчётов 50+ в разных форматах, и нужно периодически добавлять новые.
ViewController рождён в битве с back end и выкован в схватках с Apollo GraphQL. В какой-то момент back end сказало: ква, быстрее не можем. Грузите данные в tree grid порциями: сперва верхний ряд строк, потом догружайте по необходимости. Попытки выкрутить хуки GraphQL и сделать такую ступенчатую загрузку с промежуточными состояниями и маскировкой таблицы в синхронном коде довели меня до белого каления, я плюнул и добавил к ViewModel ещё и ViewController. А потом добился разрешения всё это выложить в open source. :)
Это RealWorld приложение я слабал на коленке в прошлые выходные. Сам код Statium в тестировании уже более полугода, полёт номинальный. Скоро релиз. :)