Pull to refresh
-2
0
Send message

Потому, что когда вырастаешь в юниора, то начинаешь понимать, что 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 под волшебным соусом якобы более высокой производительности? Ну нет, спасибо, такой перевод темы мне не интересен. Если вы уже забыли, дискуссия началась с того, что управление состоянием в приложении должно делаться просто и легко, потому что нет никаких весомых причин к обратному. Исследовать альтернативную вселенную псевдонаучного еслибдакабизма на тему потенциального выигрыша производительности мне недосуг.


Просто потому, что я в эту тему копал очень глубоко (нет, не в MobX, в прозводительность), и самый главный урок оказался простым: premature optimization is the root of all evil. ©

Вот смотрите, таблица с асинхронной загрузкой записей, фильтрацией и страницами: https://codesandbox.io/s/elated-buck-29fgg


За 10 минут, да. Где адские нагромождения-то? :)

Так вы сам код посмотрели?

Да я-то ваш код смотрел. Это вам я никак не могу донести свою мысль, видимо плохо объясняю.


Давайте попробуем вот так: https://codesandbox.io/s/xenodochial-snyder-hngmw


Так зачем 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?


Тем более такой, который позволяет совершенно безболезненно и за 10 минут масштабироваться от формы с тремя полями до таблицы с фильтрацией записей в реальном времени, например: https://gist.github.com/nohuhu/01a7bb42b1c1e1378ea0df6829799055


Я не вполне понимаю ваш способ чтения, потому что в самом начале 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 всегда возвращал одно и то же значение.

Не уверен, что до конца понял постановку проблемы, но что-то подобное должно работать вполне быстро: https://gist.github.com/nohuhu/01a7bb42b1c1e1378ea0df6829799055


Набросал за 10 минут, попробовал — при отображении 1000 записей фильтрация отрабатывает с едва заметной задержкой. В профилировщике самая медленная функция вне React — это FaaC в теле компонента Table, отрабатывает за 1.8 мс собственного времени. Это подход влоб, без оптимизаций.


У меня, конечно, мощный лаптоп и условия не приближенные к боевым, но я как-то плохо представляю себе отображение 1000 записей на экране даже телефона.


Или я всё же как-то неправильно понял задачу?


Ах да, прошлый раз забыл: а что там по рефакторингу? Если я захотел переименовать user в profile — мне идти по всему коду и менять надпись? Селекторы как абстракция придуманы не просто так (в mobx это проще, потому что всегда прямая ссылка на поле, ts справится с переименованием).

Не совсем понятно, что вы имеете в виду под селекторами. Если меняется имя ключа в модели, то конечно же нужно менять привязки. Как вариант, можно использовать HOC:


import { withBindings } from 'statium';

const withFoo = Component => withBindings('foo')(Component);

В таком случае имя ключа надо будет менять только в одном месте.


На практике, так ли часто это нужно? В наших проектах 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.


А форма с тремя полями? Засекайте время:


import ViewModel, { Bind } from 'statium';

const state = {
    username: '',
    email: '',
    password: '',
};

const FormWithThreeFields = () => (
    <ViewModel initialState={state}>
        <Bind props={[
                ['username', true], ['email', true], ['password', true]
            ]}>
            { values => (
                <form>
                    <input type="text" placeholder="User name"
                        value={values.username}
                        onChange={e => { values.setUsername(e.target.value); } />

                    <input type="email" placeholder="E-mail"
                        value={values.email}
                        onChange={e => { values.setEmail(e.target.value); }} />

                    <input type="password" placeholder="Password"
                        value={values.password}
                        onChange={e => { values.setPassword(e.target.value); }} />
                </form>
            )}
        </Bind>
    </ViewModel>
);

export default FormWithThreeFields;

… 3 минуты, это я опечатываюсь много.

Спасибо за критику! Нет, правда, мне очень интересны критические/негативные отзывы. Это возможность узнать о проблемах и быстрее их решить. :) Текущая версия библиотеки всего 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:


import { useBindings } from 'statium';

const MyComponent = () => {
    const [foo, bar] = useBindings('foo', 'bar');

    ...
};

Что интересно, я тоже думал, что хуки сократят и упростят код компонентов. На практике оказалось, что мне самому существенно быстрее и проще использовать <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 можно хранить объекты, и вообще любые структуры данных. Селекторами можно лезть глубоко в структуру, как для чтения, так и для записи:


import ViewModel, { useBindings } from 'statium';

const initialState = {
    foo: {
        bar: {
            baz: 42,
        },
    },
};

const Component = () => (
    <ViewModel initialState={initialState}>
        <CounterButton />
    </ViewModel>
);

const CounterButton = () => {
    const [[baz, setBaz]] = useBindings([['foo.bar.baz', true]]);

    return (
        <button onClick={() => { setBaz(baz + 1); }}>
            Click me!
        </button>
    );
};

Никакой магии тут нету, кстати сказать, банальные lodash.get/set. Ну, и чуточку обвязки.


Как "прервать" / отбросить / отменить асинхронный метод? Что если я уже успел покинуть viewModel, которое еще не успело что-то загрузить, при этом оно перепишет какое-то значение родителя?

Вы мысли мои читаете? :)) Только час назад открыл этот тикет: https://github.com/riptano/statium/issues/13.


На текущий момент ничего страшного не случится, просто в консоль будут сыпаться надоедливые нотации React о том, что нельзя обновлять состояние демонтированного компонента. Как эту проблему решить, пока думаю, вариант с исключениями вполне подходит, но душа просит чего-то более элегантного.


С архитектурной точки зрения мне кажется, что таких штук допускать не надо. Данные надо держать близко к коду. На практике, конечно, нужно с этим что-то делать. Идеи приветствуются. :)


Да, а вот вариант с перезаписыванием значения в родительской ViewModel мне в голову пока не приходил, спасибо. Т.е. в случае:


const Parent = () => (
    <ViewModel initialState={{ foo: 'bar' }}>
        <Child />
    </ViewModel>
);

const Child = () => (
    <ViewModel initialState={{ bar: 'qux' }}
        controller={{
            handlers: {
                event,
            },
        }}>
        ...
    </ViewModel>
);

const event = async({ $set }) => {
    await new Promise(resolve => { setTimeout(resolve, 10000); });
    $set('foo', 'baz');
};

… если event уже выстрелил, а потом Child демонтируется, то… хм… Надо тест написать, я не уверен, что произойдёт в текущем коде. Скорее всего значение foo изменится на новое, без ошибок.


К слову, как в методе контроллера получить значение чужой viewModel?

Если это родительская модель, то просто $get('foo') — все ключи вышестоящих моделей доступны детям (но не наоборот). Если это не родственная модель, то никак. Это фича. Выносите ключи в общего родителя.


Самое главное: зачем это все, если есть useReducer и useContext, которые позволяют получить все тоже самое? Ну вместо $set будет у вас dispatch({type: 'set'}), обертку можно легко сделать.

Так 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 в тестировании уже более полугода, полёт номинальный. Скоро релиз. :)

1
23 ...

Information

Rating
Does not participate
Registered
Activity