Pull to refresh

Comments 193

Остаётся только один вопрос: если у вас есть информация о непосредственных зависимостях любого состояния, а значит вы точно знаете каке узлы в дереве нуждаются в перерисовке, то на кой чёрт вам сдался этот реакт с его перегенерацией виртуального дерева с последующей реконциляцией и поиском узлов, которые нуждаются в перерисовке?

Я, как разработчик бизнес-логики и логики приложения, понятия не имею ни о каком дереве перерисовок. А разработчик презентационного компонента понятия не имеет ни о зависимостях состояния, ни о его изменениях.
Может я не понял о чем Вы, но к примеру взять массив сданными. по которым строится список.
В первый проход я построил список, а при следующих изменениях данных что? Если я каждый раз буду список создавать заново на уровне дом-дерева, то это не производительно. Получается что мне нужно написать свои функции, которые будут способны рендерить только изменения. Но я больше чем уверен, что разнообразность задач, поставит перед фактами, что одним только этим не обойтись. И в итоге так и придется писать свой аналог реакта или чего-то подобного. Поэтому проще взять готовое и к тому же будет проще найти специалистов, которые точно в Вашем самописе не разбираются.
Тут скорее речь о том, что на границе MobX и React теряется информация о зависимостях, которую рендерер мог бы использовать для точечного ререндинга прямо в DOM без построения нового, пускай и виртуального.
Не понимаю, как это? Вы имеете ввиду, что если mobx использовать без реакта, с обычными дом-элементами, то появляется возможность передать данные из стора в дом-элемент так, чтобы при обновлении стора точечно обновлялись дом-элементы? Разве такое возможно?

Да, вы всё правильно поняли. Это не просто возможно, но и гораздо эффективней. Изменилось что-то в сторе — перезапустилась функция обновления соответствующего участка дома.

Да, но если вас этих участков DOM изменилась тыща, то потребуется тыща обращений к DOM, а это — медленно.
React в этом случае всю тыщу изменений произведёт в виртуальном DOM, а в реальный сделает одну единственную вставку, что быстрее, т.к. в настоящий DOM будет всего одно обращение.

Да нет, Реакт сделает ровно столько же обращений к ДОМ, только ему для того, чтобы понять какие именно обращения к ДОМ необходимы — придётся делать сравнение виртуальных домов. Ну собственно, ваш пример с обновлением в 1000 местах — Ангуляр с точечными обновлениями уделывает Реакт в 2 раза.

Поможет ли здесь использование PureComponent?

Не поможет, ибо данные обновляются полностью.

Ок. Если данные обновляются полностью, то ReactJS перерендерит всё, от App и ниже в своём виртуальном DOM, а результат вставит на место #ROOT_APP сделав всего одно обращение к реальному DOM. Разве не так?

Не так, это было бы слишком медленно. Он пробежится по всем дом-узлам и проверит, чтобы их состояние соответствовало виртуальным дом-узлам.

vintage прав. Если бы это было не так, вы бы наблюдали побочные эффекты, такие как: потеря фокуса, положения курсора ввода, сброс выделения контента, положения прокрутки внутри элементов и т.п.
KasperGreen, прошу прощения, проглядел. vintage не прав — по настоящим DOM-узлам Реакт, конечно, бегать не будет.

Но и вы не правы — перерисовывать все Реакт тоже не будет, иначе это приведет к побочным эффектам, о которых я писал.

Реакт просто сравнит старое и новое виртуальное дерево и применит изменения к DOM-дереву.

Не точно выразился, да. По настоящим узлам он в итоге пробежится и обновит ибо все данные меняются.

В общем, из того, что все данные изменились не следует, что изменилось все виртуальное дерево.

Теперь во всём. Похоже я живу в мире иллюзий, но и в этом я не уверен. Как собственно и в том, что vintage не потомок Сима.

Там с картинкой некая проблема в том, что все интересное — по сути содержится в блоке «is same as real dom node from previous render», а остальное — несущественные мелочи.
А каким образом так вышло, что mol-jsx работает вдвое быстрее, чем mol, и требует меньше памяти? Я думал, что mol-jsx — что-то вроде обертки над чистым mol, логично, что он должен работать медленнее?

Меня другое интересует. Как $mol умудряется в принципе обогнать Native DOM?

Очевидно за счёт ленивого рендеринга, который позволяет не только не рендерить то, что не изменилось, но и не рендерить то, что не видно.

То есть вариант на мол не быстрый, а просто ничего не делает. В чем тогда смысл такого сравнения? Что с чем сравнивается?

К слову, клик по блоку в мол тормозит сильнее всего. Ангуляр работает практически с той же скоростью, что и нейтив, реакт — посередине.

Он далеко не "самый быстрый". Всё же ленивость и реактивность — не бесплатны. Но приложения на нём получаются самые отзывчивые. Именно потому, что он не делает то, что можно не делать. Но это синтетический бенчмарк. Вот более реалистичный.


К слову, клик по блоку в мол тормозит сильнее всего

Сомнительно. Может вы плавную анимацию фона посчитали за тормоза?

> Он далеко не «самый быстрый».

Да он вообще не быстрый.

Смотрите, этот тест направлен на то, чтобы оценить скорость рендеринга элементов фреймворком. Это предполагает, что элементы _будут_ рендериться. В результате можно оценить, как поведет себя фреймворк в ситуациях, которые сильно нагружают рендерер. Как ведет себя в таких ситуациях ваш фреймворк? Неизвестно, вы предпочли это скрыть.

> Но приложения на нём получаются самые отзывчивые.

Откуда это следует? Вы же тесты не хотите привести.

> Сомнительно. Может вы плавную анимацию фона посчитали за тормоза?

Не заметил никакой анимации.

> Но это синтетический бенчмарк. Вот более реалистичный.

Небось, там тоже все, что за пределами экрана — не рендерится, а по-этому оценить скорость работы мол нельзя?

Нет, он нацелен на то, чтобы оценить отзывчивость приложений. Пользователя не волнует сколько чего у вас будет рендериться. Более того, пользователь предпочтёт, если рендериться будет как можно меньше, а не будет впустую тратить батарейку его девайса. С точки зрения UX наиболее важная характеристика не "число отрендереных элементов в секунду", а "время между нажатием кнопки и отображением результата". Именно это и замеряется в вышеозначенных бенчмарках.

> Нет, он нацелен на то, чтобы оценить отзывчивость приложений.

А чтобы ее измерить — надо понять скорость рендера. В итоге из вышеприведенных тестов мы можем понять, насколько отзывчив реакт или ангуляр, но не можем понять, насколько отзывчив $mol. Надо полагать — совершенно не отзывчив и очень силньо тормозит, иначе разработчик фреймворка не прилагал бы таких усилий, чтобы скрыть результаты тестов.

> С точки зрения UX наиболее важная характеристика не «число отрендереных элементов в секунду», а «время между нажатием кнопки и отображением результата». Именно это и замеряется в вышеозначенных бенчмарках.

Замечательно. Как мне узнать, какое время будет между нажатием кнопки и отражением результата в $mol, когда 10к блоков будут видны на экране?

Понимаете, случай с малым количеством блоков на экране никого не интересует — потому что он при ленивом рендеринге обрабатывается за время, неотличимое от мгновенного, на _всех_ фреймворках. И даже если ваш мол в данном случае дает задержку в 2мс вместо ангуляровских 5мс — какое кому до этого дело? Пользователь не увидит разницы.
А чтобы ее измерить — надо понять скорость рендера.

Чтобы её измерить надо взять приложение, кликнуть и измерить через сколько времени закончится рендеринг. Абстрактная "скорость рендеринга" — не более чем бесполезные попугайчики.


иначе разработчик фреймворка не прилагал бы таких усилий, чтобы скрыть результаты тестов

Нет никакого секрета. Можете уменьшить объём данных, чтобы они все попадали в видимую область — тогда будет рендериться всё и $mol окажется в самом конце. Но важно даже не это, а то, что не зависимо от объёмов данных задержка не превышает 200мс.


Как мне узнать, какое время будет между нажатием кнопки и отражением результата в $mol, когда 10к блоков будут видны на экране?

Давайте вы перестанете фантазировать про 10к видимых блоков. Я уже объяснил, почему это не реалистично.


при ленивом рендеринге обрабатывается за время, неотличимое от мгновенного, на всех фреймворках.

Какой ещё фреймворк умеет в ленивый рендеринг? Именно фреймворк. То, что ручками вы можете накостылять ленивость куда угодно — с этим никто не спорит. Но это требует дополнительных телодвижений, которых никто не делает пока не начинает припекать.

> Но важно даже не это, а то, что не зависимо от объёмов данных задержка не превышает 200мс.

Там сейчас задержка на клик порядка секунды при 10к блоков, а вы хотите сказать, что он за 200мс все перерисует?

> Давайте вы перестанете фантазировать про 10к видимых блоков. Я уже объяснил, почему это не реалистично.

Давайте вы не будете объявлять вполне обычные кейзы «нереалистичными», а просто скажете, как поведет себя $mol в этом случае и какая будет задержка?

> То, что ручками вы можете накостылять ленивость куда угодно — с этим никто не спорит.

Так а что еще надо, учитывая, что ленивость эта нужна в паре-тройке мест во всем приложении (а большинстве случаев и вовсе не нужна никогда)? А вот быстрый рендеринг — нужен практически всегда.
Там сейчас задержка на клик порядка секунды при 10к блоков, а вы хотите сказать, что он за 200мс все перерисует?

Где "там"? Если вы про реализацию на $mol_dom_jsx, то там не используется ни реактивность, ни ленивость, ни реконциляция. Там дубовая реализация, аналогичная native dom. С той лишь разницей, что используется JSX для шалонизации.


Давайте вы не будете объявлять вполне обычные кейзы «нереалистичными», а просто скажете, как поведет себя $mol в этом случае и какая будет задержка?

Описание реалистичного кейса в студию.


ленивость эта нужна в паре-тройке мест во всем приложении (а большинстве случаев и вовсе не нужна никогда)?

Описание кейса, когда ленивость является лишней, в студию.

> Где «там»? Если вы про реализацию на $mol_dom_jsx

Я про обычный $mol.

> Описание кейса, когда ленивость является лишней, в студию.

Проще указать, когда является — если вам надо вывести какой-нибудь список на много-много позиций, и пагинация в данном конкретном случае плоха. Во всех остальных кейзах — ленивость является лишней и вредной.

> Описание реалистичного кейса в студию.

Я же вам указал — грид. Только не с пустыми ячейками, а с контентом.
Я про обычный $mol.

И как вы этого добились?


Во всех остальных кейзах — ленивость является лишней и вредной.

Обоснуйте.

> И как вы этого добились?

Кликнул по блоку. Выше же говорил об этом уже, клики мол обрабатывает медленнее всех.

> Обоснуйте

Никакой пользы она не приносит, а накладные расходы — остаются.
Кликнул по блоку. Выше же говорил об этом уже, клики мол обрабатывает медленнее всех.

Не наблюдаю у себя ни в одном браузере. Поможете воспроизвести?


Никакой пользы она не приносит, а накладные расходы — остаются.

Высокая отзывчивость, плавные анимации, экономия батарейки — это бесполезные штуки?

> Высокая отзывчивость, плавные анимации, экономия батарейки — это бесполезные штуки?

Так у вас и без ленивой загрузки будет такая же высокая отзывчивость и плавные анимации. Единственное, где может быть выигрыш — это в плане батарейки, но это еще смотреть надо, сколько там выйдет.

> Не наблюдаю у себя ни в одном браузере. Поможете воспроизвести?

Первый приведенный вами бенчмарк, ставим 10к блоков, мотаем на середину, выделяем один блок, потом выделяем соседний, первый блок гаснет где-то через полсекунды-секунду. Никакой анимации при этом не видно (анимация есть при наведении на блок)
Так у вас и без ленивой загрузки будет такая же высокая отзывчивость и плавные анимации.

Ну конечно, делаем больше работы, а отзывчивость не проседает.


Первый приведенный вами бенчмарк, ставим 10к блоков, мотаем на середину, выделяем один блок, потом выделяем соседний, первый блок гаснет где-то через полсекунды-секунду.

Тут было две причины:


  1. Не кешировалось свойство row_selected, из-за чего все 10к элементов обновляли свои атрибуты при изменении текущего элемента. Поправил.
  2. Бага в хроме вызывает адские фризы при клике. Тоже поправил.

Теперь не тормозит. Спасибо :-)


Заодно обновил Реакт до 16 версии. Первичный рендеринг замедлился, зато обновление ускорилось.

> Ну конечно, делаем больше работы, а отзывчивость не проседает.

Да нет, просто дело в том, что нету реальной разницы между 100фпс и 200фпс.

Открываем то же самое на мобилке и видим разницу между 10фпс и 20фпс.

Скажите, а ваш мега-ленивый $mol поисковыми роботами вообще индексируется? А Ctrl+F на ленивой странице работает?

> А Ctrl+F на ленивой странице работает?

Уже обсуждали. Вместо ctrl+f предлагается пользоваться специальным костыле-поиском на странице.

Для поисковых роботов всё нелениво рендерится на сервере.


Ctrl+F очевидно не найдёт то, что не отрендерено. Также он не найдёт то, в принципе не выводится (идентификаторы сущностей, например) или выводится в графическом формате (статусы, альтернативный текст картинок). И наоборот он найдёт кучу лишнего (например, каку-нибудь ерунду из рекламного блока в сайдбаре).


Можете провести юзабилити тестирование и посмотреть что выберут пользователи:


  1. Долгое открытие страницы, зато потом можно что-то найти по ctrl+f.
  2. Быстрое открытие страницы, но ctrl+f находит не всё.
  3. Быстрое открытие страницы, на котором есть поле фильтрации, которое позволяет отсеить нерелевантные данные.
  4. Долгое открытие страницы, на котором есть поле фильтрации, которое позволяет отсеить нерелевантные данные, но и можно поискать по ctrl+f.
Пользователь выберет быстрое открытие страницы с ctrl+f и полем фильтрации.
Дмитрий, а если рендер сделать не ленивым, а отложенным, с RAF-синхронизацией? Тогда мы будем иметь:

  • быстрый начальный рендер страницы (данные вьюпорта рендерятся сразу же, остальное отложенно)
  • быстрый рендер по клику при открытии (т.к. нет ленивого рендера)
  • поиск по Ctrl+F
  • поле фильтрации

Ну только не говорите мне, что это невозможно реализовать.

Это более чем возможно. Но опять же:


  1. Пока не закончится рендеринг всего, ctrl+f не будет полноценно работать. А асинхронный рендеринг займёт куда больше времени, чем рендеринг сразу всего. Так что ctrl+f будет работать непредсказуемо для пользователя, что ещё хуже, чем просто работает / не работает.
  2. Пока идёт рендеринг того, что пользователь не видит — будут наблюдаться подтормаживания при работе с тем, что он видит. Поток всего один, а превысить 16мс — легко.
  3. Далеко не факт, что пользователь пойдёт потом мотать скролл до самого конца. В этом случае мы жрём батарейку впустую ради… возможности искать по ctrl+f?
  4. Чем больше элементов в ДОМ, тем медленнее происходит reflow, repaint и прочее.
  5. Чем больше мы нарендерили, тем больше потребуется видео-памяти (пропорционально площади всего контента в пикселях), которой на мобилках — кот наплакал.
1 и 2 — React Fibers создавались как раз с этой целью.

3 — а вот для реализации этого юзкейза я участвовал в разработке подобного, но для html5 canvas, а не DOM. Так что, как видите, это не просто факт, а достаточно факт, чтобы превратиться в требование от стейкхолдера.

4 — для этого опять же существует vDOM. Например React.

5 — никуда не деться и я даже не знаю, что хуже: отсутствие привычных хоткеев или прожорливое приложение. 4-5 лет назад это было критическим пунктом, но не уверен, что это сейчас имеет смысл.

Пятый пункт — единственный, где ещё могут возникнуть вопросы. По остальным пунктам — это ответ на ваш самый первый комментарий к этой статье.
У файбера есть и недостатки: плавность, ценой пропуска кадров. Это проявляется, если проц слабый или загружен. Откройте 10 демок на файбере и в одной повозите мышкой над цифрами. Проблему производительности он скорее маскирует, чем решает. Может быть когда-нибудь в многопоточной среде и будет решать, но не в однопоточной.

Про скорость repaint в DOM, мне кажется, Дмитрий имел в виду, что если на странице много элементов, в том числе и за пределами видимой области, то это будет тормозить. И vDOM тут никак не поможет.

vDOM не бесплатный, поэтому есть shouldComponentUpdate, отчасти setState, а также много попыток написать быстрее, чем в react (тот же inferno или ivi).

vDOM — это оптимизация внизу, перед выводом в браузер. А mobx-подобные решения — это оптимизация наверху, сразу после изменения данных. Что может быть гораздо быстрее любого vDOM, т.к. глубина вычислений будет меньше в среднем случае.

Многие фреймворки добиваются высокой производительности, используя точечные обновления, вместо vDOM (тот же angular4, отчасти vue). Интересно наблюдать, как развивается этот альтернативный подход, в том числе и в mol.
Да, маскирует, но это как раз и является целью. Нам и нужно отложенно отрендерить большой контент без фризов и с быстрым начальным рендером.

Если он имел ввиду это, то у нас юзкейзы с гридом, а браузеры уже научились оптимизировать paint и отбрасывать невидимые элементы. Слабо себе представляю пример, чтобы на экране были одновременно все дивы видимых грида, кроме выдуманного где-то здесь использования дивов в качестве пикселей.

vDOM не бесплатный, конечно же, но он быстрей, чем DOM. И его тоже надо уметь готовить. А чтобы использовать Inferno, всё равно надо соблюдать определённые соглашения и рекомендации. Итого получается то же самое, что и с ручной оптимизацией в узких местах. Доводилось слушать автора Inferno на конференции.

Мне тоже интересно, конечно же. Истоки этого обсуждения в том, что автор мола хочет объяснить, что React не нужен. Пока не объяснил)
Всегда ли плавность важнее гарантированного отклика? Да и почему, собственно, понадобилось добавлять какой-то Fiber для видимости ускорения? Для меня убедительно выглядит пример треугольника на mol, который обеспечивает плавность без технологий, вроде Fiber. И для этого надо раз в 5 меньше кода и это все написал один человек, а не команда из гиганта FB.

Используя mobx, мы большую часть оптимизаций перекладываем с vDOM на mobx. Если эту идею довести до логического конца, то зачем тогда vDOM?.. А если не нужен vDOM, то некоторая часть реакта становится бесполезным оверхедом. Автор mol как раз это показал, написав небольшой наколеночный jsx-процессор на нативном DOM, сопоставимый с vDOM по производительности.

Рекомендации будут в случае любого фреймворка. Тут вопрос, не является ли часть из них следствием неудачных решений, принятых где-то раньше? Что было бы с рекомендациями, если бы перед реактом сделали mobx?

Вместо redux мы бы сразу получили mobx-state-tree с его децентрализованным стейтом и другими фичами.

shouldComponentUpdate стал бы не нужен, т.к. mobx решает эту проблему автоматически, не привнося сложности в прикладной код. Observer просто делает shallowEqual при любом обновлении данных.

componentWillMount стал бы не нужен, т.к. актуализацию данных можно делать в роутере или в самих данных (через fromPromise и lazyObservable из mobx-utils). О чем и говорит Michel Weststrate в статье How to decouple state and UI
Гарантированный отклик важнее, конечно же. Именно поэтому приоритет операций от пользователя максимальный: см. SynchronousPriority.

Вопросом про нужность vDOM и оптимизации в MobX вы вернули всю дискуссию в самое начало и отправили по второму кругу. Так что давайте зайдём с другой стороны. Так вот, суть использования MobX — это обсёрваблы, подключённые к React-компонентам. После отправки данных в пропсы MobX никак не сможет повлиять на реконсиляцию. И выбросить реакт, этот ненужный оверхед с его реконсиляцией виртуального дома, можно только при следующих условиях:
  • компоненты должны быть декомпозированы очень сильно. Чем меньше кирпичик — тем выше производительность. К чему и тяготеет мол.
  • вы должны быть уверены, что в вашем проекте нет узких мест, которые зафризят UI. Сюда включается и начальный рендер (от чего мол убежал с мотивацией «так код будет более идиоматичный») и тяжёлые обновления (а от этого мол не смог убежать — фризы на демках появляются регулярно).

Иными словами, эта «заманчивая» идея подходит только для синтетических или в корне неверных бенчмарков или мелких приложений. Но я не хочу выделять каждый див в отдельный компонент, я не хочу, чтобы список товаров умирал через 5 секунд после начала прокрутки. Прозводительность этой «наколеночной» замены vDOM (как и исходная реализация мола) сравнима с React только при совсем тепличных условиях, любое реальное приложение выходит за рамки этих условий.

Что было бы с рекомендациями, если бы перед реактом сделали mobx?
Подозреваю, что вместо реакта должно быть «ридакс»? По моему скромному мнению ничего не было бы, и до ридакса и после ридакса было много реализаций флюкса. В команде фейсбука достаточно много инженеров, радеющих за функциональный подход, чтобы продукты компании дрейфовали в сторону функциональщины. И Redux получил большее распространение не потому, что он появился раньше MobX, а потому, что он был скопирован с функционального Elm.

В целом библиотека MobX хорошая вещь, но она не конкурент Redux, они используют разные подходы, занимают разные ниши и по требованиям к продукту и по сообществу разработчиков.

И ещё пара замечаний:

shouldComponentUpdate и не нужен для того случая, который вы описали. Для этого в реакте давно есть ныне депрекейтед PureRenderMixin, который теперь React.PureComponent. Это не привносит сложности в прикладной код, просто вы наследуетесь от другого класса, и Pure… стоит использовать в 99% случаев. Для функциональных компонентов — pure HOC. Не знаю, в курсе ли вы, но в recompose есть и более вкусные вещи: onlyUpdateForKeys() и onlyUpdateForPropTypes(). ShouldComponentUpdate нужен в очень редких случаях. А когда нужен, он даёт ручное управление, что для производительности всегда будет лучше, чем умный change detection мобикса. Оптимизация рендера через обработку пропсов — это знания о преобразованиях внутри рендера, что явно ответственность компонента и должно быть рядом с компонентом. А не в сторонней библиотеке в виде универсального решения. Которое к тому же не даёт никакого выигрыша перед стандартным решением из реакта.

componentWillMount: статью эту впервые вижу. И знаете, я не использую componentWillMount)). И роутер у нас напрямую работает со стором (точнее с redux-saga). И реакт вообще там не причём, это не его зона ответственности, Мишель на пустом месте сам себе придумал проблему и сам решил её.

P.S. пример с моловским треугольником стал тормозить уже после 5-й открытой вкладки, в то время как демонстрация файбера так себя начинает вести только после 10-й или 12-й.
После отправки данных в пропсы MobX никак не сможет повлиять на реконсиляцию.
Смысл в mobx, если отправлять сырые данные в пропсы. Вот то, что в случае mobx и react, в пропсы должны попадать запакованные значения, что б магия работала, скорее это ограничение.

компоненты должны быть декомпозированы очень сильно. Чем меньше кирпичик — тем выше производительность. К чему и тяготеет мол.
Тут хотелось бы понять, какой пример мы обсуждаем: mol_jsx, mol или некий абстрактный на mobx и react без vdom на реальном dom.
Если я правильно вас понял, вы считаете, что все DOM-элементы в компоненте будут пересозданы, если какое-либо его observable-свойство поменялось.
Так реализация прослойки между mobx и DOM должна точечно мутировать DOM-ноду, если надо поменять одно из ее свойств или дочернюю ноду, как это и делает mol_jsx.

Наверное есть случаи, где такой подход будет работать хуже vDOM, но тут надо конкретный пример рассматривать и оценивать насколько это критично.
Для mol, лучше бы vintage объяснил, что будет, если делать все здоровенными компонентами.

вы должны быть уверены, что в вашем проекте нет узких мест, которые зафризят UI. Сюда включается и начальный рендер (от чего мол убежал с мотивацией «так код будет более идиоматичный») и тяжёлые обновления (а от этого мол не смог убежать — фризы на демках появляются регулярно).
Тут не понял. Как vDOM может уберечь от фризов? Что такого было бы в приложении с mobx и нативном DOM, что фризило бы рендеринг?

Подозреваю, что вместо реакта должно быть «ридакс»?
Именно реакта. Посыл был в том, что mobx делает многие вещи ненужными в реакте. Если бы проектировать начали с слоя данных, то дальше многие проблемы решать было бы проще. В Vue, кстати, в основе нечто похожее на mobx.

Почему не с redux, потому что это opinionated-подход, навязывающий определенный архитектурный стиль.
Mobx же претендует на unopinionated: обычные классы, минимум инфраструктурного кода. Нравится функциональный стиль — пожалуйста, mobx хорошая платформа для построения mst-подобных решений.
Можно было бы оставить только pure-компоненты, зоопарк способов менять состояние свелся бы к единообразному подходу на основе mobx и его надстройках. Все были бы счастливы.

shouldComponentUpdate и не нужен для того случая, который вы описали
Я показал, что в mobx, observer его реализует одинаково для всех оборачиваемых pure-компонент.

Не знаю, в курсе ли вы, но в recompose есть и более вкусные вещи: onlyUpdateForKeys() и onlyUpdateForPropTypes(). ShouldComponentUpdate нужен в очень редких случаях.
Изначальная идея — автоматически ускорить рендеринг и вдруг, для некоторых случаев, предлагается это делать вручную. Вопрос, где слабое звено в фреймворке, из-за которого возникла такая потребность? В vue как-то без shouldComponentUpdate обходятся.

Основной посыл был в том, что с mobx меньше инфраструктурного кода. Конечно со всякими redux-saga тоже меньше, но тут смысл в unopinionated, появляется выбор: можно напрямую в mobx состояние актуализировать, а можно и mst и saga-подобные решения накручивать.

P.S. пример с моловским треугольником стал тормозить уже после 5-й открытой вкладки, в то время как демонстрация файбера
Я к тому, что mol по сравнению с реактом без файбера выдавал лучшую производительность, сравнимую с реактом с файбером на одной вкладке, да.
Было б прикольно, если б vintage запилил свой файбер, но он наверное опять скажет «не нужно».

Да я пилил уже квантификацию вычислений, причём на уровне $mol_atom а не $mol_view. Но это даёт артефакты в духе "во второй половине страницы ничего не обновляется, когда в первой что-то постоянно обновляется" и тому подобные, так что пока отказался. Надо будет ещё подход сделать — может получится нормально реализовать.

Я к тому, что mol по сравнению с реактом без файбера выдавал лучшую производительность, сравнимую с реактом с файбером на одной вкладке, да.
Это не так. Прочитайте вот этот комментарий Дмитрия. Модифицируйте пример с треугольником Серпинского так, чтобы все кружочки всегда попадали на экран — мол очень сильно просядет.

… автоматически ускорить рендеринг и вдруг, для некоторых случаев… В vue как-то без shouldComponentUpdate обходятся.
Ну мы же выяснили, что shallowCompare (которое pure) есть и в реакте, мобиксовское тут ничего не добавляет. А обходятся потому что автор решил, что это не нужно. «Вам это не нужно, попробуйте организовать компоненты по-другому.» :) В моём текущем проекте всего два компонента имеют shouldComponentUpdate с кастомной функцией, остальным это не нужно. Два — это всё равно что ничего. Во Vue со второй версии тоже есть vDOM, и есть shallowCompare, но все равно есть проблемы с производительностью, к которым подойдёт ручная настройка.

Можно было бы оставить только pure-компоненты, зоопарк способов менять состояние свелся бы к единообразному подходу на основе mobx и его надстройках. Все были бы счастливы.
Я счастлив, я меняю состояние компонентов через пропсы, и далеко не всё храню в глобальном сторе. И не использую зоопарк. А подход, который вы описали, на самом деле приводит к ещё одному конкурирующему стандарту.
One more competing standard
image

Почему не с redux, потому что это opinionated-подход, навязывающий определенный архитектурный стиль.
Не понял, почему? Вот у меня все компоненты берут всё из пропсов. Коннект — это просто один из внешних хоков (HOC). Они не знают ничего про ридакс и замечательно будут работать и с MobX и с реализацией флюкса на Backbone. И функциональные и обычные class-based компоненты.

Тут не понял. Как vDOM может уберечь от фризов? Что такого было бы в приложении с mobx и нативном DOM, что фризило бы рендеринг?
Мы не совсем про vDOM говорим, а про ненужный реакт. Фризит начальный рендер, большое обновление данных за раз. Мол тут как раз замечательный пример: там используется ленивый рендер и отсечение элементов, не попадающих во вьюпорт. Если попытаться после рендера прокрутить вниз большой список, то появляются дикие фризы: мол пытается отрендерить сразу большое количество элементов (прокрутившихся мимо вьюпорта) за раз, да ещё и все вместе, никак их не кооперируя.

Если я правильно вас понял, вы считаете, что все DOM-элементы в компоненте будут пересозданы, если какое-либо его observable-свойство поменялось.
Так реализация прослойки между mobx и DOM должна точечно мутировать DOM-ноду, если надо поменять одно из ее свойств или дочернюю ноду, как это и делает mol_jsx.
Да, так, будут. Нет, не так, mol_jsx их пересоздаёт.
Модифицируйте пример с треугольником Серпинского так, чтобы все кружочки всегда попадали на экран — мол очень сильно просядет.

Вы бы не говорили глупости, а проверили сперва сами. В этой демке $mol рендерит все кружочки, даже если они не влезают в экран.

Ох, давайте я расскажу вам как сейчас работает ленивый рендеринг в $mol_view:


  1. У каждого компонента есть свойство minimal_height. Рассчитывается оно эвристически на основе содержимого.
  2. Есть компонент, который управляет скроллингом — $mol_scroll — он предоставляет информацию о нижнем крае видимой области.
  3. Есть компоненты-лейауты. Например, используемый в "демке на прокрутку" вертикальный лейаут — $mol_list. Он умеет использовать информацию о минимальном размере вложенных в него компонент и нижнюю границу скроллинга, чтобы отсечь те компоненты, которые точно не попадают в видимую область. Так как minimal_height как правило не соответствует реальным размерам компонент (они больше), то $mol_list обычно рендерит чуть дальше видимой области.

В треугольнике Серпинского ничего ленивого не используется, ибо предполагается его запускать так, чтобы он полностью влезал в видимую область.

Понятно. Ну тогда обновите демку, вы обманываете пользователей не только тем, что неправильно меряете отзывчивость (как — уже указывал), но и неправильным текстом. Потому что используете производный компонент для своего мол, а в остальных случаях — дубовая реализация.
вы обманываете пользователей не только тем, что неправильно меряете отзывчивость (как — уже указывал), но и неправильным текстом

Не обманываем. Там замеряются 2 основных параметра, важных для любой страницы — время показа и время обновления данных. Всё это с точки зрения пользователя.


Потому что используете производный компонент для своего мол, а в остальных случаях — дубовая реализация.

Во всех реализациях написан идиоматичный для фреймворка код.

Модифицируйте пример с треугольником Серпинского так, чтобы все кружочки всегда попадали на экран — мол очень сильно просядет.
Пока не понял что модифицировать, вроде столько же кружочков, у меня не тормозит.

Во Vue со второй версии тоже есть vDOM, и есть shallowCompare, но все равно есть проблемы с производительностью, к которым подойдёт ручная настройка.
Чем меньше деталей о реакте приложение знает, тем лучше, зачем лишний инфраструктурный код. По мне, лучше сперва научить фреймворк проблему решать автоматически, только когда уже все способы исчерпаны, дать еще один способ выстрелить в ногу, ручной оптимизацией. Возможно автор vue считает также.

Я счастлив, я меняю состояние компонентов через пропсы, и далеко не всё храню в глобальном сторе. И не использую зоопарк
Пропсы никто не отменяет. Отказ от сырых данных в пропсах для всех компонент это неудобно, да. В реакте пока мы видим компромисс, когда только некоторые компоненты являются observables, а дальше задача ложится на чистые компоненты и vDOM. Но это из-за того, что реакт изначально не предполагалось использовать с такими структурами данных. Vue, angular и пресловутый mol как раз более приспособлены для них, хотя в первом пока тоже vDOM на edge-cases используется.

Зоопарк бывает не только библиотек, но и идей, когда нет единообразия в смыслах и одной ответственности. Кроме чистых компонент вы используете HOC-и и компоненты с setState, если не все храните все в глобальном сторе. По мне, так уже зоопарк из 3х типов компонентов, появившихся из-за компромиссов, на которые пошли, когда менять основу было уже поздно.

Чистые кастомизируются хорошо, HOC плохо, т.к. к предоставляют биндинги к стейту на редаксе и прибиты к бизнес логике, с setState тоже самое, но без редакса и стейт с логикой уже нельзя отделить от компонента и от реакта. Понятие компонент вообще размыто. Есть presentational компоненты (View), container-компоненты с данными и логикой (Model), компоненты-контроллеры (react-router, react-helmet), компоненты-биндинги (HOC).

Mobx же позволяет больше компонентов сделать в единообразном стиле хотя бы. Почти везде будет чистый компонент с observable оберткой. Будет логика отделена от верстки, что лучше для SRP.

А подход, который вы описали, на самом деле приводит к ещё одному конкурирующему стандарту.
Я имел в виду, что если бы mobx был в основе react, то многое упростилось. Как раз меньше было бы стандартов и дублирующих функциональность библиотек, т.к. более мощное решение в основе. Да и потом, вроде на фронтенде нормально, что кто-то всегда будет искать лучшее решение. Судя по этой активности, приблизиться к идеалу ни у кого пока не получилось.

Не понял, почему? Вот у меня все компоненты берут всё из пропсов.
Компоненты — только часть приложения. А как же всякие actions, reducers, middlewares — это все opionated, т.к. навязывает определенный стиль, функциональный подход, свои соглашения по модификации состояния и т.д. У mobx эти соглашения проще и ближе к нативному js, в статье об этом говорится, причем на этой абстракции можно строить сложные, вроде mst.

Да, так, будут. Нет, не так, mol_jsx их пересоздаёт.
Не пойму, почему вы так решили. В том комменте говорится, будут пересозданы, если id-шника нет. Судя по реализации там есть переспользование и в примере видно, что не пересоздается нода. Может мы о разных нодах говорим?
Пока не понял что модифицировать, вроде столько же кружочков, у меня не тормозит.
Я смотрел на оригинальную демонстрацию, а не на его, и вот там нет скейла .75 и на небольшом 17" экране ноутбука не полностью влезает. Дмитрий уже рассказал, что в этой демке используется по-другому mol_view, не обёрнутый в контроллер, предоставляющий вьюпорт :) Так что вопрос про модификацию отпал.
Чем меньше деталей о реакте приложение знает, тем лучше, зачем лишний инфраструктурный код. По мне, лучше сперва научить фреймворк проблему решать автоматически, только когда уже все способы исчерпаны, дать еще один способ выстрелить в ногу, ручной оптимизацией..
Знаете, а я тоже так думаю :) И вот по-моему как раз реакт — лучший пример такого подхода. Компоненты принимают просто параметры извне, а всю работу поручают собственно реакту. Но они не забыли про ручную оптимизацию) А вот нет, автор Vue как раз так не считает (вы читали issue по ссылке?). Он прямо говорит, что если ваш компонент слишком часто перерисовывается, то просто разбейте его. Конечно же тогда shallowCompare разобьётся на столько отдельных функций, сколько компонентов будет создано, каждая на вход будет принимать меньше изменившихся пропсов. Это та же ручная оптимизация, но в ущерб читабельности и в ущерб поддержке кода. Куда логичней вынести это в отдельную функцию, правильно? Которую по-хорошему можно и переиспользовать (экстракт в отдельный файл и использование как хок). Вот ещё прямой ответ, что не нужен shouldComponentUpdate… потому что не нужен.

Кроме чистых компонент вы используете HOC-и и компоненты с setState, если не все храните все в глобальном сторе.
Здесь и далее много неточностей. Мне нравится реакт, но не нравятся многие подходы, принятые в мейнстриме реакт-сообщества. У нас нет разделения на тупые и умные компоненты, грубо говоря они все умные (использование каких-то тупых промежуточных компонентов, которые будут трансферить пропсы в дерево нижележащихся компонентов считаю абсолютно неверным подходом, напрочь убивающим какую-либо возможность реюза и усложняющим модификацию UI). В целом подход к организации одного компонента очень близок к Vue с его однофайловыми компонентами, только у нас однопапочные компоненты.

У нас нет setState, только хоки и ридакс, в сторе лежит минимум данных — только те, которые нужны для апи и некоторые глобальные производные. Кстати, какие же хоки биндинги? Это же просто high orders functions. У нас нет трёх типов компонентов — всё просто функциональные stateless компоненты. react-router считаю громадной ошибкой. react-helmet — это чудо тоже используем, но какой же это контроллер? Он работает так же, как сборка стилей для критического CSS при SSR.

В итоге: всё является чистыми функциями (насколько это возможно в js). Компоненты (написанные в едином стиле), хоки, селекторы, actionCreator'ы и редьюсеры — всё чистые функции. Сайд эффекты в сагах. Это всё неприлично просто покрывается юнит-тестами, упрощает модификацию по сравнению с классическим построением реакта.

Возможно я нахожусь в каком-то локальном экстремуме, но я совершенно не вижу, как можно большой объём переиспользуемой логики (продукт — личный кабинет, высокодинамичный SPA с SSR) представить в виде вороха компьютед и обсёрваблов. И получить те же компоненты в едином стиле, которые сейчас у нас есть.

Я имел в виду, что если бы mobx был в основе react, то многое упростилось. Как раз меньше было бы стандартов и дублирующих функциональность библиотек, т.к. более мощное решение в основе.
Ну вот в Ангуляре есть те же самые обсёрваблы. Что-то там не очень упростилось. В чём подвох?

Сам Мишель пишет, что Mobx в этом плане ближе к Ember и Knockout, обе либы набрали популярность до появления реакта. Обе предоставляют те же самые обсёрваблы и компьютед. Как видим, все эти условия существовали уже (чтобы подобная реализация была в основе реакт). Почему же реакт был сделан на чистом листе?

Компоненты — только часть приложения. А как же всякие actions, reducers, middlewares — это все opionated, т.к. навязывает определенный стиль, функциональный подход, свои соглашения по модификации состояния и т.д. У mobx эти соглашения проще и ближе к нативному js, в статье об этом говорится, причем на этой абстракции можно строить сложные, вроде mst.
Насчёт ближе к нативному js — ой как спорно, ведь что может быть проще, чем просто функции и просто объекты. Наверно вы имели ввиду, что больше свободы.

Не пойму, почему вы так решили. В том комменте говорится, будут пересозданы, если id-шника нет. Судя по реализации там есть переспользование и в примере видно, что не пересоздается нода. Может мы о разных нодах говорим?
Верно, id-шника нет — значит пересоздаём, потому что неизвестно какая нода: без явной ручной привязки выяснить это можно только сравнением нового и старого vDOM. Ничего такого в mol_jsx нет, там вообще нет реконсиляции. Это не виртуальный дом, а просто jsx-адаптер, о чём честно и написано.
Вот ещё прямой ответ, что не нужен shouldComponentUpdate… потому что не нужен.

Не нужен. Там человек пытался вырывать гланды через анус — ему объяснили, что так делать не правильно. Ему надо было чтобы содержимое не обновлялось, пока его редактируют, а shouldComponentUpdate вырубил бы вообще все обновления (даже атрибутов и свойств).


Ну вот в Ангуляре есть те же самые обсёрваблы. Что-то там не очень упростилось. В чём подвох?

Там другие обсёрваблы.

> У нас нет setState, только хоки и ридакс, в сторе лежит минимум данных — только те, которые нужны для апи и некоторые глобальные производные.

А где хранится стейт компонент?

> Почему же реакт был сделан на чистом листе?

Потому что стояла задача портирования существующего пхп кода со вполне определенной архитектурой (принимаем запрос с параметрами — рендерим по этим параметрам страницу). На обсерваблы и компьютеды это не ложится, зато ложится на чистые компоненты.

> Ну вот в Ангуляре есть те же самые обсёрваблы. Что-то там не очень упростилось. В чём подвох?

В том, что обсерваблы в ангуляре для апдейта дом не используются, так что нет, не «в ангуляре те же самые обсерваблы».
А где хранится стейт компонент?
Используем recompose, выносящий хранение стейта в хоки. Благодаря этому компонент просто получает пропсы и не знает, откуда они приходят.

Потому что стояла задача портирования существующего пхп кода со вполне определенной архитектурой (принимаем запрос с параметрами — рендерим по этим параметрам страницу). На обсерваблы и компьютеды это не ложится, зато ложится на чистые компоненты.
Это был вопрос, на который я не ждал ответа) Моя цель была показать, что Mobx-подобные подходы уже были, и что они не повлияли на реакт (а может и повлияли, но в обратном ключе: показали, как не надо делать).

А про саму суть вопроса — я читал немного другое: что цель стояла как раз сделать подход, подобный XHP (автоматическая обработка xml-like синтаксиса для безопасности), для работы с динамическими приложениями в браузере. И что на реакт повлиял Clojure и создание Om как обёртки реакта (на CloseScript), что привело и к зависимости реконсиляции от иммутабельных структур.

Т.е. дело не в наследии от PHP, а в наличии адептов функционального программирования.
> Используем recompose, выносящий хранение стейта в хоки. Благодаря этому компонент просто получает пропсы и не знает, откуда они приходят.

То есть внутренный компонент может менять внешний стейит из НОС?

> Моя цель была показать, что Mobx-подобные подходы уже были, и что они не повлияли на реакт (а может и повлияли, но в обратном ключе: показали, как не надо делать).

Они на него не повлияли потому что были неприменимы в одном конкретном случае, для которого разрабатывался реакт.

> Т.е. дело не в наследии от PHP,

Прямо по вашей ссылке:

> ReactJS started as a JavaScript port of XHP, a version of PHP which Facebook released four years ago.

> а в наличии адептов функционального программирования.

Уже это обсуждалось. В реакте функционального программирования не больше, чем в любом другом фреймворке. Ну и вообще странно ожидать от кондовых пехепешников из фейсбука любви к функциональным подходам :)
То есть внутренный компонент может менять внешний стейит из НОС?
Внутренний компонент получает функцию, которую повесит на onClick, и строку, которую положит в значение спана. Он не знает, откуда эти функция и строка пришли — от ридакса или от хока.
Но они не забыли про ручную оптимизацию)
Это как повернуть. На мой взгляд это следствие того, что vDOM не спасал в сложных компонентах и на больших глубинах вложенности. Естественно никто не скажет, мы такие растяпы, не учли это сразу, поэтому простите нас, вот хотя бы shouldComponentUpdate, в ангуларе с его update strategies еще похлеще.

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

Из того issue в vue:
The fully framework-compliant way to deal with it (and is also what I would suggest even in React) is to have a separate piece of state that represents the input content, which can then be out of sync with the model state and can be updated only when you want to.
Я вот согласен с этим и это вовсе не «не нужен shouldComponentUpdate… потому что не нужен». Если выбирать между shouldComponentUpdate и более детальным разбиением на компоненты, лучше последнее.

Здесь и далее много неточностей.
Много, что б было меньше, не могли бы вы привести пример, как вы готовите редакс с сагой? Если на 5м уровне вложенности вам надо галочку на интерфейсе отобразить, тоже через редакс делаете или клепаете экшен/редьюссер? У вас один центральный стор или много инстансов редакса в приложении? Иерархия модулей плоская или фрактальная? Кто делает bootstrap начальных данных саги очередного компонента мини-приложения? Как решается, какой компонент обмазывать HOC-ами и сагами, а какой использовать чистым? Как достигается типобезопасность в HOC, что стейт пришедший в connect, имеет свойство blaBla?

Мне нравится реакт, но не нравятся многие подходы, принятые в мейнстриме реакт-сообщества
Вот-вот, реакт отличается множеством способов выстрелить в ногу, т.к. основа не дает цельного, масштабируемого инструмента и спецификации для комфортной разработки. Поэтому в экосистеме столько решений с дублирующейся функциональностью. Дело конечно еще и в громадном накопленном опыте решений, ради сохранения совместимости костыли неизбежны, неизвестно что еще в дальнейшем произойдет с vue и т.д. Мне интересно, кто оказажется прозорливее и угадает лучше. Вроде vue пока многие вещи пока лучше угадывали.

react-helmet. Суть в том, что на компонентах пытаются программировать не только верстку. Все эти конструкции в верстке, нарушают разделение ответственности, изоляцию и размывают границы между слоями. Уже непонятно, что верстка, а что управляющая конструкция. react-helmet — ничего не выводит в компонент, он сайд-эффект, меняющий title, поэтому я и назвал его контроллером. Эта хрень не должна быть в компонентах. Все это напоминает олдскул php, с его запросами к базе посреди шаблона, только замаскированный под красивый xml-синтаксис.

Кстати, какие же хоки биндинги? Это же просто high orders functions. У нас нет трёх типов компонентов — всё просто функциональные stateless компоненты.
Ох, тут много можно сказать, раз уж пошла речь о HOC. HOC как раз и будет трансферить пропсы в дерево нижележащихся компонентов. Поэтому я и говорил биндинги, намекая на его функцию. Название high orders functions вводит в заблуждение, т.к. это не единственный способ делать связывание компонент с логикой, а задача то именно эта решается.

В ангуларе например, есть нативный конструктор класса, типы и DI для этих же целей. При этом HOC редакса рождает намертво прибитый к редаксу, путям в стейте и экшенам компонент и в этом заключается его непереиспользуемость, нарушается OCP: нельзя расширить компонент без рефакторинга.

Сага с редаксом, правда, дает возможность большую часть логики убрать из HOC, сделав его очень тонким. Правда при этом нарушается принцип инверсии контроля, когда зависимости верхнего уровня (точка входа) знают о деталях нижних, в виде редьюсеров и саг. И это не избавляет от шаблонного кода для связывания, вроде dispatch(bla-bla). Инверсия контроля же позволяет декларативно на любом уровне иерархии переопределять эту связь и для любого компонента вообще, у винтажа похоже, кстати, с его контекстами.

Еще HOC усложняют вывод типов, т.к. всякие connect могут по сложной схеме менять контракт компонентов. Маппинг реальных пропсов в то, что получается на выходе connect не однозначен. Это может быть поправят, т.к. в flow, наряду с костылями «спешал фор реакт», добавили костылей «спешал фор редакс», вроде $Diff или $ObjMap. Правда пока в flow-typed их почему-то не используют. Это не говоря о таких вещах, как вывод типов state в mapStateToProps, что вообще не представляется возможным с текущим flow.
Вывод типов в redux connect
function A(props: {a: number, b: string, onClick: () => void}) { return null }

const mapStateToProps = (state, props) => {
  return {
    active: props.c + props.a
  }
}

const mapDispatchToProps = (dispatch, props) => {
  return {
    onClick: () => {
      dispatch({some: props.filter})
    }
  }
}

const AHoc = connect(mapStateToProps, mapDispatchToProps)(A)
flow/try

Возможно я нахожусь в каком-то локальном экстремуме, но я совершенно не вижу, как можно большой объём переиспользуемой логики (продукт — личный кабинет, высокодинамичный SPA с SSR) представить в виде вороха компьютед и обсёрваблов
Если обсервабл выглядит как обычный класс с методами и свойствами, зависимости которого в конструктор инжектятся, то тестировать и мокать легко. Это кому что удобнее, вечный холивор ООП vs ФП. Никто не лучше, инструментов и там и там достаточно. У меня другая жизненная школа, я за ООП, SOLID и т.д, поэтому экспериментирую с этим в реакт, т.к. уверен, что результат будет понятнее и проще ООП-шникам.

Ну вот в Ангуляре есть те же самые обсёрваблы. Что-то там не очень упростилось. В чём подвох?
О, не те же самые. Все дело в акцентах. Попробую систематизировать сорта «обсерваблов»:

RxJS, beacon, kefir, most, pull-stream, ramda, отчасти es observable — разной степени удачности и монстроидальности попытки сделать ФП в js. Отличаются навязчивым API. Т.е. приложения на них — это вкрапления нативного синхронного кода в апи этих библиотек. Вы выстраиваете поток простых вычислений, вставляя их в огромное кол-во точек расширения библиотеки или заменяете все операции на функции. Грубо c = add(a, b) вместо c = a + b. Для ООП-шников это выглядит как слишком много мусора и, в целом, сложно для освоения. Лично я считаю, если в языке нет из коробки средств для ФП, то не надо их туда тащить, это не в философии языка.

Baobab и д.р. переходные формы, популярные пару лет назад решения на основе single state observable дерева, концепция проще чем у стримов, т.к. в основном есть только источник observable и производная computable.

mobx — дальнейшее упрощение концепции стримов, когда стримы замаскировали под нативные классы и объекты, избавив ООП-шников, коих большинство, от изучения новых концепций и дав свободу от архитектурных стилей, можно самому лепить из классов что угодно. Фишка mobx в автоматическом связывании частей вычисления, когда вы пишите c = a + b и этого достаточно, что б при обновлении a, обновился и c. Т.к. это упрощение, многие, но не все задачи на нем красиво и просто решаются, поэтому Мишель в своем в своем faq и написал When to use RxJS instead of MobX.

mol_atom от vintage, cellx от Riim и lom_atom моя имплементация атомов Дмитрия для реакта — Еще более упрощенный mobx, есть только одна сущность — атом. Причем данные сами синхронизируют себя с асинхронным стором по факту обращения к ним. Это ключевой момент, отличающий их от всего, что мне известно. То, с чем vintage носится столько лет и говорит как про pull, вытягивание. Напоминает идею инверсии контроля, только для observable-данных.

Используете вы RxJS, mobx (хотя и в нем можно через хелперы, но как я выше говорил, все дело в акцентах), редакс с сагой или еще что, ответственность по актуализации лежит за потребителем данных, componentDidMount, bootstrap саги с ручным явным вызовом или не важно как там это замаскировать.

Нарушается инкапсуляция и SRP, т.к. факт необходимости загрузки — это деталь, которая просачивается в вышележащий уровень. В сложных приложениях, при рефакторинге нижнего уровня, неизбежно надо рефачить верхний, что черевато багами. Это винтаж имеет в виду, когда говорит про багоемкость.

То, что технология не популярна, может также означать, что у автора нет соотвествующих качеств/ресурсов для ее популяризации. Первоначальную идею я встретил у винтажа в его jin.atom в 2014 году и никакого mobx-а тогда небыло. Идея c = a + b теперь такая же в mobx. Ноосфера, она такая, тут Мишель верно угадал и смог продать.

Насчёт ближе к нативному js — ой как спорно, ведь что может быть проще, чем просто функции и просто объекты
Спорно, так спорно. Это не просто функции и объекты. Важен смысл, который за ними, в mobx этих смысловых сущностей меньше и они выглядят проще, иными словами более простой core concept, который ближе к нативным классам. user.name = 'test' вместо dispatch({type: 'CHANGE_USER_NAME', name: 'test'})

Вообще, я имел в виду приведенную вами статью Мишеля:
Probably, it is more accurate to talk about MobX as a data flow library, that enables you to roll your own state management architecture with minimal effort.

Верно, id-шника нет — значит пересоздаём, потому что неизвестно какая нода
Ваше заявление было категорично: «Нет, не так, mol_jsx их пересоздаёт.». Это не так, если id-шник есть. По мне, малая плата для достижения эффекта точечного обновления DOM без vDOM и реконсиляции. Вот то, что JSX для идентифкации кодовых блоков подходит хреново и совсем без ручной расстановки id-шников не обойтись, это другой вопрос. В идеале бы свойство языка таким сделать. Кроме этого, идентификация помогает идеально следовать принципу open/close во всем приложении, чего пока нет нигде, кроме mol. Вот поэтому vintage и выдумал свой tree и пытается его продать. В этом есть рациональное зерно.
Много, что б было меньше, не могли бы вы привести пример, как вы готовите редакс с сагой? Если на 5м уровне вложенности вам надо галочку на интерфейсе отобразить, тоже через редакс делаете или клепаете экшен/редьюссер? У вас один центральный стор или много инстансов редакса в приложении? Иерархия модулей плоская или фрактальная? Кто делает bootstrap начальных данных саги очередного компонента мини-приложения? Как решается, какой компонент обмазывать HOC-ами и сагами, а какой использовать чистым? Как достигается типобезопасность в HOC, что стейт пришедший в connect, имеет свойство blaBla?

Где именно хранить состояние галочки — это вопрос насколько эта галочка должна быть доступна в других частях приложения и должно ли это состояние синхрониться с сервером. Не очень понял разницу «редакс или экшен/редьюсер». Стор один. Если галочка локальная и влияет только на доступность какого-то соседнего блока, то делается хок (пусть хок#1) сразу для компонента 5-го уровня, который хранит состояние этой галочки и передаёт его как пропс. Всё, галочка рисуется там, где надо.

Предположим галочка теперь приходит из апишки и кладётся в стор сагой, тогда для конкретной вьюшки обновляется селектор (чистая функция), который знает о нужных ему данных и отдаёт их в новый хок#2 (который connect от react-redux) сразу к этому компоненту на 5-ом уровне (вместо хок#1). Этот хок#2 не изменится, если затем будет меняться апишка или структура данных, меняется только селектор. Всё, галочка рисуется там, где надо. Файл с чистым компонентом затронут не был.

Если под иерархией модулей подразумевается структура папок, то сейчас она фрактальная. Плоская не подходит из-за слишком большого количества компонентов.

Совсем не понял про бутстрап данных саги, у нас нет мини-приложений, постоянно работает разное количество саг. Компоненты все чистые и все обмазаны хоками и выглядит это примерно вот так. Только у нас энхансеры (презентационная логика) и сам компонент (только вёрстка) лежат отдельно, в соседних файлах.

Ни компоненты ни энхансеры не знают ничего про саги вообще, они знают только про пропсы. Компоненты в свою очередь не знают ничего про энхансеры, а только про пропсы. Саги ничего не знают про компоненты, они меняют состояние стора только через actions, а при необходимости берут состояние через те же чистые селекторы, которые выше уже указывал (в примере про галочку). Ничего про структуру стора сага не знает, при изменении структуры обновить надо будет только редьюсеры и селекторы. Все соучастники преступления, описанного выше — чистые функции (кроме саг, конечно же).

Типобезопасность в самих хоках сейчас пока никак не решается, это происходит только в селекторах и в компонентах (flow-аннотации для пропсов всех компонентов). Но connect-хоки (которых порядка 5%-10% от всех хоков) состоят только из селекторов. Общие переиспользуемые хоки не типизированы (в беклоге задача).

react-helmet. Суть в том, что...
Понял вас) Да, мне тоже не нравится, когда jsx пытаются использовать как управляющие конструкции, это меня как раз сильно отталкивает от react-router. У нас helmet используется только в одном месте для удобного вывода в head, чтобы работать с jsx, а не со строками в dangerouslySetInnerHTML. Все сайд-эффекты через саги.

При этом HOC редакса рождает намертво прибитый к редаксу, путям в стейте и экшенам компонент и в этом заключается его непереиспользуемость, нарушается OCP: нельзя расширить компонент без рефакторинга.
К путям, допустим, не прибиты, а в остальном да, закрытый для расширения наследованием получается. Открытый только для композиции.

Сага с редаксом, правда, дает возможность большую часть логики убрать из HOC, сделав его очень тонким. Правда при этом нарушается принцип инверсии контроля, когда зависимости верхнего уровня (точка входа) знают о деталях нижних, в виде редьюсеров и саг.
Не очень понял, какие точки входа знают про саги. Сага вещь в себе, она работает только со стором, через стандартные actions и стандартные selectors (эти же экшены и селекторы используются для компонентов).

Ваше заявление было категорично: «Нет, не так, mol_jsx их пересоздаёт.»
На самом деле мы изначально говорили про работу MobX-подобных решений без реакта. И про размер вёрстки в каждом компоненте. Не помню, скидывал ли я тогда, но Мишель сам прямо говорит: дробите компоненты как можно меньше, чтобы меньше перерендеривать. И то же самое справедливо для mol_jsx: обычный компонент будет полностью перерендериваться.

Хорошо, если там есть поддержка id-шников, то вы готовы в поддерживаемых приложениях для каждого дива проставить айдишник?

P.S. И вообще мы далеко отошли от основного вопроса — бессмысленности реакта при наличии mobx. Предлагаю либо закругляться, либо в личку перейти)
Не очень понял разницу «редакс или экшен/редьюсер».
Да, я ошибся, имелось в виду setState или экшен/редьюссер. Но теперь я понял про recompose с withState. Для меня важен подход, когда есть прозрачность интерфейсов, везде и точно работает выведение типов без костылей вроде utility types. Когда типизация помогает, а не мешает, когда есть единообразие и простота реализации SOLID (хотя бы SO) — это индикатор того, что путь верный. Как только появляются строки для работы со стейтом, возникают затруднение с выведением — это плохой признак.

По мне, даже setState был бы лучше этого. Напомитает подход, который был в React.createClass, описание класса через фабрику, нестандартную спецификацию библиотеки.

Даже если забить на выведение типов, которое тут никогда не заработает, вы действительно считаете, что вот это

Пример на compose
const enhance = compose(
  renameProp('imageUrl', 'thumbnailUrl'),
  withState('name', 'setName', props => props.name),
  withState('favourites', 'setFavourites', []),
  withState('favouriteText', 'setFavouriteText', ''),
  withHandlers({
    updateName: ({ setName }) => event => {
      setName(event.target.value);
    },
    updateFavouriteText: ({ setFavouriteText }) => event => {
      setFavouriteText(event.target.value);
    },
    addListEntry: ({
      setFavourites,
      favourites,
      favouriteText,
      setFavouriteText
    }) => event => {
      event.preventDefault();
      setFavourites([favouriteText, ...favourites]);
      setFavouriteText('');
    }
  })
);

const FavoritesExt = enhance(Favourites);


Проще и понятнее, чем это?

Пример на setState
class FavoritesExt extends Component {
  constructor(props, context) {
    super(props, context)
    this.state = {
      name: props.name,
      favourites: [],
      favouriteText: ''
    }
  }

  updateName = ({target}) => { this.setState({name: target.value}) }
  updateFavouriteText = ({target}) => { this.setState({favouriteText: target.value}) }

  addListEntry = (e) => {
    e.preventDefault()
    this.setState({
      favourites: [this.state.favouriteText, ...this.state.favourites],
      favouriteText: ''
    })
  }

  render() {
    return Favorites({
      ...this.props,
      thumbnailUrl: this.props.imageUrl,
      ...this.state,
      addListEntry: this.addListEntry,
      updateName: this.updateName,
      updateFavouriteText: this.updateFavouriteText
    })
  }
}


Если уж пытаться добавлять сахар, то по мне лучше так:

Пример на reactive-di
class FavoritesService {
  @mem favouriteText = ''
  @mem favourites: string[] = []
  @mem name = ''
  @props set props({name}: {name: string}) {
      this.name = name
  }
  addListEntry = (e: Event) => {
    e.preventDefault()
    this.favourites = [...this.favoriteText, this.favourites]
    this.favoriteText = ''
  }
  updateName = ({target}: Event) => { this.name = target.value }
  updateFavouriteText = ({target}: Event) => { this.favouriteText = target.value }
}

function FavoritesHoc(_: {name: string}, service: FavoritesService) {
  return Favorites({...service})
}


Заметьте, последний пример вообще от реакта не зависит, только от JSX и HOC-ов там в принципе нет. compose же зависит, в нем внутри обычный container-компонент на реакте.

то делается хок (пусть хок#1) сразу для компонента 5-го уровня, который хранит состояние этой галочки и передаёт его как пропс.
HOC — это всегда привязка к конкретной реализации чего-то. В реакте выбора особо нет. Если мало HOC — куча пропсов тянутся до ближайшего HOC, повышая сложность рефакторинга. Много HOC — возрастает сложность переиспользования и появляется копипаст, когда берут тот же чистый компонент и лепят поверх новый HOC. Завязки на реализации проникают на все уровни иерархии приложения.

Предположим галочка теперь приходит из апишки и кладётся в стор сагой, тогда для конкретной вьюшки обновляется селектор (чистая функция)
Возможна только модификация существующего кода, вместо расширения. Вот если бы так работало Хок2(Хок1(Компонент)), как при наследовании. Получается следуем одному принципу в ущерб другому (либо хок, либо возможность расширения), подобная дихотомия много где в реакте встречается.

Файл с чистым компонентом затронут не был.
Есть же не только компоненты. Все, что вокруг получается не переиспользовать. Нужен компонент с таким же поведением, но с другой копией стейта — придется копипастить. Проблема в отсутствии единообразия с сохранением расширямости в любых частях приложения, которая нужна для фрактальной масштабируемости.

Не очень понял, какие точки входа знают про саги. Сага вещь в себе, она работает только со стором, через стандартные actions и стандартные selectors (эти же экшены и селекторы используются для компонентов).
У вас же центральный стейт, так? Где-то в index.js есть место, где регистрируются все редьюссеры, все саги. Это — детали реализации конкретных частей приложения, утекающие в index.js. По какому событию вы загружаете начальные данные очередного компонента в процессе работы с приложением, где bootstrap происходит? А точно это должно быть в том месте и в то время?

Хорошо, если там есть поддержка id-шников, то вы готовы в поддерживаемых приложениях для каждого дива проставить айдишник?
mol_jsx proof of concept. Я ж написал, что JSX плохой инструмент для идентификации кодовых блоков. Да и много ньюансов есть, не везде надо их проставлять, только если в компоненте ветвления и т.п. Да и так ли страшно это пересоздание, это ж не во всех кейсах происходит. В choo например, на это забили, ну да в тестах он не блещет, но вполне работоспособен для нетяжелых данных. Есть гораздо более легковесные real dom diff алгоритмы, например nanomorph. У винтажа в mol свой diff, с блекджеком.

P.S. И вообще мы далеко отошли от основного вопроса — бессмысленности реакта при наличии mobx
Если развивать идею mobx и под нее делать экосистему, то да, vdom не нужным становится, а diff алгоритм сильно упрощается. Тот вброс Дмитрия в любом случае приведет к обсуждению целого ряда недостатков реакта и экосистемы вокруг него, это системная проблема. Я думаю, со временем архитектурная критика будет только нарастать, например как в «React-стек: скрытые угрозы», Илья Климов
.

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

Забавно, что именно в Реакте так в основном и делают.
А что вас смущает в том, что каждый див — отдельный компонент? $mol_view компонент — не более чем легковесная реактивная обёртка над дом-элементом.


я не хочу, чтобы список товаров умирал через 5 секунд после начала прокрутки.

Вы хотите, чтобы он умирал сразу, если программист не позаботился о паджинации?


Прозводительность этой «наколеночной» замены vDOM (как и исходная реализация мола) сравнима с React только при совсем тепличных условиях, любое реальное приложение выходит за рамки этих условий.

Пример "выхода за рамки условий" приведёте? И, кстати, что за условия?

«Так все делают» — это плохой аргумент. Я так не делаю. Мои знакомые так не делают.

Нет, не хочу. И тут мы прошли по кругу и вернулись к началу. А говорили про отложенный (не ленивый!) рендер.

Под условиями я имел ввиду выход за рамки маленькой демки. Вы сами ниже с горем пополам согласились, что дивы будут пересоздаваться, если явно не указать id-шники им. И никакие обсёрваблы этому не помогут (хотя да, помогут, если каждый див вынести в отдельный mol_view). Это не говоря про то, что должно быть маленькое приложение, а чем оно больше разрастается — тем больше становится запутанными взаимосвязи между вьюшками-дивами. Отлаживать это всё добро, да ещё и со странными стектрейсами — то ещё удовольствие. Объединение компонентов ожидаемо приводит к падению производительности. Ибо вся магия в ручном разделении.
Вы сами ниже с горем пополам согласились, что дивы будут пересоздаваться, если явно не указать id-шники им.

Вообще-то я сразу подчеркнул, что дом-узлы в случае $mol_dom_jsx реиспользуются по идентификаторам. И это лишь пример jsx рендерера без vdom. Можете написать свой с автогенерацией идентификаторов в духе реактовых "1.3.2.1.2". Но я за человекопонятные идентификаторы.


$mol_view же хоть и добавляет идентификаторы, но не зависит от них. Там каждый компонент сам создаёт себе узел и кеширует его у себя.


чем оно больше разрастается — тем больше становится запутанными взаимосвязи между вьюшками-дивами.

Не становятся. Ну вот, например, приложение для чтения статей с ленивой загрузкой, отображением индикатора загрузки, визуализации маркдауна и боковым меню. Все взаимосвязи весьма наглядно представлены.


Отлаживать это всё добро, да ещё и со странными стектрейсами — то ещё удовольствие.

Что странного в наших стектрейсах?


Объединение компонентов ожидаемо приводит к падению производительности.

Любой компонент является композицией других компонент. Ни к какому падению производительности это не приводит.

Можете написать свой с автогенерацией идентификаторов
Вы просили указать — я указал. Если писать полноценный vDOM, то он превратится в (P)React. Что и требовалось доказать. Ваше залихвастское утверждение «да я тут за 5 минут на коленке написал ваш ненужный на самом деле вдом» не подтвердилось.
весьма наглядно представлены
Очень интересно, когда вы агрессивно рекламируете свою библиотеку, но при этом напрочь игнорируете чужое мнение и напропалую используете демагогические приёмы. Нет, tree не нагляден. Нет, я говорил не про демки. Давайте начнём хотя бы с 10k sloc.
Если писать полноценный vDOM, то он превратится в (P)React.

Не нужно писать vDOM.


вы агрессивно рекламируете свою библиотеку

Процитируйте, пожалуйста, "агрессивную рекламу".


напрочь игнорируете чужое мнение и напропалую используете демагогические приёмы

А вам не приходило в голову, что ваше мнение может оказаться неверным, и люди не соглашаются с вами не потому, что они такие упёртые бараны?


Нет, tree не нагляден.

Обоснуйте.


Давайте начнём хотя бы с 10k sloc.

http://mol.js.org — 25ksloc

UFO just landed and posted this here

У Яндекса своя вязанка велосипедов: bemhtml, bemjson, bemtree и другие.


Кстати, ждем reActive Tree c проприетарным кодом в нодах.

Это что такое?

Тоже не понимаю, зачем вы его написали :) Вы совершенно не собирались достигать заявленных целей написания mol_jsx, вместо этого с радостью окунулись в демагогию.

люди не соглашаются с вами
Моё мнение?) Мне совершенно не интересно, соглашаетесь вы или нет со мной. Вообще-то это я с вами не соглашаюсь и вместе с другими порасспрашивал про то, что вы понаписали. Расспросил, спасибо. Теперь мне более понятно, что вы понаписали, поэтому вы перешли к упёртым баранам и точкам зрения :)

Что обосновать? Я так думаю, мне он просто не нравится (как и coffescript, например). Я знаю ваш ответ, он предсказуем: вот он, за два года не утратил актуальность, вы по-прежнему так отвечаете всем, кто с вами не согласен :) И да, это с вами люди в большинстве своём не соглашаются, судя по ссылке выше, по этому посту и так далее. И вы зря считаете, что это потому, что вы никем не понятый одиночка, ваяющий бриллиант во тьме невежества. Vue.js успел стать популярным, хотя был написан одиночкой (и он до сих пор поддерживается практически только им), а заматерев, перешёл на ненавистный вами vDOM.

mol.js.org — 25ksloc
Вот точка входа, судя по всему? А рядышком в meta.tree инклуды, где 6 десятков других tree-файлов (которые кушаются вместе с .ts). Даже с учётом простой плоской структуры сего одностраничника выглядит крайне неудобно и малопонятно где какие компоненты используются. Не говоря уж про то что совершенно не ясно, как это всё работает с ts. Эта демонстрация получилась ещё более неудачной.

P.S. Вы очень зря постоянно пытаетесь вести себя в таком стиле, это отталкивает :) И совершенно зря, указывая на различия во мнениях, считаете их закостенением и/или конформизмом. Этим вы ещё более отталкиваете и собеседника и читателей. Мы все не первый год живём, некоторые даже успели повзрослеть, много раз меняли мнения на разные вещи. Не говоря уж про программирование: мы все наблюдали, как меняются языки, подходы и инструменты. И менялись вместе с ними.
Вот точка входа, судя по всему?

Нет, точка входа тут: https://github.com/eigenmethod/mol/blob/master/app/demo/index.html#L31


А рядышком в meta.tree инклуды, где 6 десятков других tree-файлов (которые кушаются вместе с .ts).

Само приложение показывает демонстрации всех компонент, что есть в бандле. В meta.tree указывается какие ещё компоненты включить в бандл, так как от них нет прямых зависимостей нигде в коде. В типичных приложениях meta.tree не используется для подключения компонент, ибо есть прямые зависимости в коде.


Не говоря уж про то что совершенно не ясно, как это всё работает с ts.

В документации всё написано. view.tree транслируется в view.tree.ts, который содержит сгенерированные классы. Через view.ts можно эти классы расширить своей логикой.


Нет, tree не нагляден.

… потому что...


мне он просто не нравится

Чудесно.

Ну смотрите, когда водишь мышью над кругами — на всей странице перестают обновляться числа. Считаете ли вы это нормальным?



Да, $mol не размазывает вычисления по фреймам, поэтому наблюдается небольшая просадка fps при обновлении чисел. На мой взгляд это лучше, чем полное отсутствие обновления.


Ну и собственно, можете заглянуть в профайлер. (Кстати, чтобы найти этот комментарий мне пришлось воспользоваться гуглом, ибо ни ctrl+f ни паджинация мне никак не помогли со страницей всех моих коментариев.)

Неубедительная аргументация, если честно. Артефакт может зависеть от загрузки процессора, версии файбера и расстановки приоритетов компонентам.

Во-первых, надо уточнить случаи, когда
рендеринг каждого кадра важнее скачков анимации и доказать, что это так.

Во-вторых, «полное отсутствие обновления» — экстраполяция. Лучше уточнять параметры. Т.е. на слабых мобилках, при неправильно расставленных приоритетах может и да, ваш способ лучше, но это пока не делает файбер костылем.
Не хочу вас огорчать, но на моём домашнем компьютере $mol фризится (не тормозит, а именно фриз на некоторое время) при обновлении чисел, в то время как React работает адекватно.

Да с чёго бы мне расстраиваться? Адекватно — понятие относительное. Ну вот я замедлил свой комп в 5 раз:


  • React15 — 1 фпс
  • React16 — 60 фпс, числа не обновляются вообще
  • $mol — 60 фпс, фризы на 150мс при обновлении чисел

Кстати, я тут заметил, что $mol выдавал низкий фпс (13) в этом тесте между обновлениями чисел. Причин опять оказалось 2:


  1. $mol_view актуализирует всё состояние дом-узла, когда что-то меняется в данных. Тут точки рендерились прямо в корень, что приводило к тому, что при изменении стиля корневого элемента $mol_view проверял так же и в правильном ли порядке расположены точки. Решение — поместить точки во вложенный элемент. В Реакте, если что, сделано так же.


  2. Использование touch-action отличный от auto приводит хром к безумно долгому обновлению дерева слоёв. Так что не стоит его использовать без крайней необходимости.
Странно, Core i7-4550U 1.50GHz и 61-м хроме на убунте

10 вкладок с треугольником на mol летают (видимо я уже обновленную версию открыл), грузят где-то 10%, а на реакте под 100% все ядра грузят и вычисления стоят на одной цифре, правда анимация наиплавнейшая.

У вас, я так понимаю срабатывает трюк: если вкладка не видна, вычисления засыпают.

У меня Реакт тоже пессимизируется в фоновых вкладках, правда не сразу, а через несколько секунд. Думаю тот дело в том, что $mol_atom все вычисления откладывает в requestAnimationFrame, который перестаёт работать сразу при скрытии вкладки. В Реакте же вычисления инициируются сразу в обработчике setTimeout а в requestAnimationFrame откладываются лишь, через 10мс работы. А setTimeout в фоновых вкладках пессимизируется не так радикально, как requestAnimationFrame.

Ну смотрите, когда водишь мышью над кругами — на всей странице перестают обновляться числа. Считаете ли вы это нормальным?

Конечно считаю ненормальным. Но вы обманываете сами себя, в реакте нет такого. Эта демка была сделана давно и на нестабильной ранней имплементации.

Сейчас не надо оборачивать setState в ReactDOMFiber.unstable_deferredUpdates, как и самого ReactDOMFiber не стало.

Ещё один обман: это не бенчмарк, а сравнение плавности анимации в реакт15 и в реакт16. Ну и да, присоединюсь к людям выше, у меня тоже мол намного медленней работает, чем вы рассказываете.

Я попробовал подключить версию 16.0.0 — получил 2.5 фпс. Ну да, в 3 раза быстрее React15 :-)
Если вы знаете как сделать правильно, чтоб не тормозило — сделайте пул-реквес (https://github.com/nin-jin/sierpinski), а не рассказывайте, что я сам себя обманываю.

Возможно имеет смысл обновить знания про файбер, поковырять исходники, выглядит несколько умнее, чем вам кажется.

Есть механизм установки дедлайнов, приоритетов, насколько я понял, он теперь различает анимацию и приоритизирует ее ниже, чем пользовательский ввод, может не рендерить невидимые узлы и т.д.

А пример с серпинским устаревшим выглядит, других пока нет, к сожалению.
vintage, что вы хотите достичь? Этот треугольник серпинского — заведомо сложный пример для реакта, как и для любой библиотеки, которая не отсекает сама дивы за краем экрана. А в каждый кружок специально добавлен долгий цикл на 80 миллисекунд. Браузер, конечно, отменяет прорисовку дивов вне вьюпорта, но эти вычисления обходятся куда дороже, чем вывод этого маленького дива.

Раз уж вы хотите померяться письками с реактом, то вот вам демка: build-mbfootjxoo.now.sh. В js указан sourcemap, так что исходники там есть (понятия не имею, есть ли оригинальный репозиторий в открытом доступе).

Треугольник серпинского для актуальной версии мне тоже интересно будет сделать. Но не вижу никакого смысла добавлять его в ваши «бенчмарки» :)
Этот треугольник серпинского — заведомо сложный пример для реакта

Который был написан самими фейсбуковцами для рекламы файбера. Это не "мои бенчмарки".


как и для любой библиотеки, которая не отсекает сама дивы за краем экрана

Это демо вообще не про отсечение. Если у вас треугольник не влезает в экран — просто уменьшите зум.


А в каждый кружок специально добавлен долгий цикл на 80 миллисекунд.

В том-то и дело, что не в каждый кружок, а в каждую группу кружков/подгрупп и не 80мс, а по 0.8мс на группу. Именно поэтому реакт и тормозит, что ему чтобы обновить значения кружков нужно пересчитать все группы, а это долго. Использование реактивного программирования позволяет не пересчитывать группы на каждый чих. Поэтому $mol обновляет содержимое кружков за 35мс, а Реакт — за 385мс. А всё, что Делает РеактФайбер — размазывает эти 385мс на 40 кадров.

Который был написан самими фейсбуковцами для рекламы файбера. Это не «мои бенчмарки».

Анимацию он хорошо показывает по-сравнению со старым реактом и производит вау-эффект, для этого наверное и создавался. Примеров мало, что б делать выводы какие-то.
Который был написан самими фейсбуковцами для рекламы файбера. Это не «мои бенчмарки».
Не знаю, что вы себе напридумывали, продолжая обманывать себя, но он не написан для рекламы файбера. Он написан для того, чтобы показать разницу между двумя версиями реакта. Если бы они рекламировали скорость работы рендера, то сделали бы летающих по странице 10 тысяч пикачу.

Точно, 0.8 мс) Но нет, реакт не тормозит тут. Это ненормальные условия, где реконсиляция занимает намного больше обновления ДОМа. Это замедление было сделано специально, чтобы показать разницу между двумя версиями реакта. Мол получает тут сравнимую скорость только из-за того, что эти библиотеки разные, и ненормальные условия по-разному влияют на них. В реальных приложениях такого замедления нет (ваш к.о.), мол не получит такого выигрыша.

Дмитрий, вы не согласны, что это не бенчмарк?))
Это ненормальные условия, где реконсиляция занимает намного больше обновления ДОМа.

Не реконциляция, а рендеринг приложения в vDOM. Какой-нибудь сложный фильтр в реальном приложении запросто может занять и куда больше времени.


Терминологические споры мне вести не интересно.

Да, я именно это и имел ввиду.

А я не предлагаю)) Просто мне кажется забавным, как вы ухватились за эту демку.

И какой же сложный фильтр может быть в функции рендера?))

Товары с популярностью выше среднего в тексте названия, типа и описания которых встречаются подстроки "сини" и "штан".

Какая ужасная у вас функция рендера. Ну да ладно, на вкус и цвет все фломастеры разные. Боюсь в этой ветке уже совсем нечего обсуждать, но вы хотя бы попытались. У вас остались шансы получше использовать демагогию в других ветках, а ведь демагогия — ваш конёк :)

У вас есть в стейте список товаров и список фильтров. Вам надо нарисовать отфильтрованный список товаров. Куда вы засунете фильтрацию?

1 — размазывание рендеринга по фреймам никак не поможет ctrl+f полноценно заработать раньше окончания всего рендеринга. В худшем случае он может вообще никогда не завершиться, если постоянно будут более приоритетные задачи (скриптовая анимация какая-нибудь в верху страницы).


2 — Реакт размазывает по фреймам лишь свой маленький кусочек работы. Есть ещё большой пласт работ по подготовке данных, по отрисовке оных и прочие задачи, на которые банально может не хватить времени до конца фрейма, большая часть времени которого уже потрачена на рендеринг невидимых частей.


3 — очевидно, ваше требования было в том, что бы работал поиск. Необходимость оного никто не оспаривает. Я объяснил, чем нативный поиск плох и почему ему лучше предпочесть встроенный. На нормальном фреймворке с двусторонним связыванием такие поиски делаются в 3 строчки кода.


4 — reflow (обновление дерева css-блоков), repaint (отрисовка css-блоков), recomposition (формирование итоговой картинки) — это части браузерного пайплайна, которые отрабатывают уже после внесения изменений в DOM.


5 — ничто не мешает по ctrl+f фокусировать встроенный поиск.


Вы попробуйте своё прожорливое приложение на среднестатистической мобилке.

Первым делом уточню: я нигде не требовал поиск по Ctrl+F. Возможно вы меня путаете с предыдущими собеседниками :) Более того, благодаря переходу на файберс в реакте в ближайшее время появится возможность оптимизировать рендер, отменяя операции для элементов, не попадающих во вьюпорт, и я этого очень жду) Ctrl+F в этом случае тоже ожидаемо не будет работать.

1. См. пост выше про обман себя. У реакт@16 нет такого, что он может не вывести числа. Полный рендер большой портянки гридов завершится не за один присест с фризом, а за несколько кадров без фризов. Для пользователя это будет относительно незаметно, так как большая часть элементов будут уже не видны, а первые (видимые во вьюпорте) появляются сразу же.

2. Но мы же говорили именно про оптимизацию рендера :) Проблемы с тяжелой работой с данными, не относящиеся к реакту, будут в любом случае, это отдельный вопрос, никак не относящийся к нашему обсуждению. В остальном — кооперативная мультизадачность тасков рендера в реакте поддерживает приоритет операций, и для операций от пользователя он наивысший.

3. Нет, моё требование было в том, чтобы пользователь мог тут же проскроллить к нужному месту большую портянку. Она должна быть сформирована как можно раньше, отложенно, но не лениво. Ctrl+F у нас вообще был невозможен, это же canvas, как упомянул выше. В случае мол это является большой проблемой, которую вы игнорируете с завидным упорством. А в ваш бенчмарк "Время вывода значительных объёмов данных в WEB приложениях" надо добавить ещё один шаг: пара десятков прокруток на произвольные места отрендеренного списка. Но этого никогда не будет сделано, так как вы любите обманывать себя.

4. Ваши исходные слова:
Чем больше элементов в ДОМ, тем медленнее происходит reflow, repaint и прочее.
Вы что, думаете реакт по отдельности каждый див вставляет?) А минимизировать изменения ДОМа как раз реакт помогает.

5. Это сводится к тому же самому:
— у вас в демке с вашим мол скролл фризит намертво всю страницу!
— а вы скролл не дёргайте.

В итоге вы предлагаете костыль вместо привычного решения. И не факт, что полнотекстовый поиск вообще реализован. И не всегда его можно будет реализовать) Например на страничке с 22-я агрегированными списками (внутренний инструмент для отчётов по разным срезам). Мы там наоборот специально пришли к браузерному Ctrl+F как к наиболее удобному решению. И да, мы костыль с перехватом Ctrl+F никак не решает основной проблемы: возможности сразу начать скроллить. Молу от этого плохеет настолько, что страница фризится намертво.

То наше приложение из пункта 3 работало на IPad 2. Который старый, а не новый mini.
Проблемы с тяжелой работой с данными, не относящиеся к реакту, будут в любом случае

Если что-то можно не рендерить, то можно и не подготавливать для него данные и даже можно их не загружать. Ленивый рендеринг имеет системный эффект. Пока вы только ещё готовите данные для передачи их реакту для рендеринга портянки блоков, я уже покажу те, что видимые.


Нет, моё требование было в том, чтобы пользователь мог тут же проскроллить к нужному месту большую портянку.

К нужному месту скроллбаром у него не получится. А вот поиск бы помог.


Но этого никогда не будет сделано, так как вы любите обманывать себя.

Не поэтому, а потому что это довольно маргинальные кейсы. Если для вас они важны — можете заморочиться и запилить пул-реквест.


Вы что, думаете реакт по отдельности каждый див вставляет?)

Вам сюда: https://webo.in/articles/all/2009/31-rending-restyle-reflow-relayout/


В итоге вы предлагаете костыль вместо привычного решения.

Я настаиваю на удобном для пользователя решении. Стандартный поиск весьма не удобен.

Мы реакт обсуждаем. С чего вы взяли, что мой сервис/сага будут готовить данные для невидимых компонентов?))

Итого мы прошли кучу обсуждений, где вы доказывали, что всё работает и всё получится, и теперь заявляете, что не получится. Спасибо, что рассказали :)

И какие же это маргинальные кейзы, если каждый второй человек в комментариях вам писал об этом?) Н

Вы прямо светоч знаний. Правда по-прежнему непонятно, зачем вы это в ответ на необходимость отрендерить список. Только не говорите, что вы пугаетесь много рендерить и из-за этого даёте пользователям страдать, когда все первым делом тянутся к скроллу и мол фризит вкладку :)

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

С того, что Реакт имеет ведомую архитектуру. Что для него подготовили — то он и отрендерил. $mol, наоборот имеет, ведущую архитектуру — что он запросил, то для него и подготовили. Вам, чтобы инвертировать архитерктуру, нужно будет вручную поплясать вокруг детектирования видимых элементов данных в реальном времени и подготовкой только нужного.


И какие же это маргинальные кейзы, если каждый второй человек в комментариях вам писал об этом?)

Поиск малейшего изъяна и выдача его за огромную дыру — типичная реакция, когда покушаются на святое.

Вам, чтобы инвертировать архитерктуру, нужно будет вручную поплясать вокруг детектирования видимых элементов данных в реальном времени и подготовкой только нужного.
Простите, но я же сам говорю реакту, что рендерить :) Что же такого уникального в ваших десяти строчках? Только вы правильно умеете плясать вокруг детектирования и расширять точку до кругозора? :) Вы в курсе, что реализаций виртуального скроллинга великое множество?))

Поиск малейшего изъяна и выдача его за огромную дыру — типичная реакция, когда покушаются на святое.
Понятно, вопросов больше нет :D

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

О том и спич, что вам нужно всё-время самому ему говорить что рендерить. Мне же достаточно сказать "сам разберись какие данные тебе нужны и запроси их у меня". Вы бы не смайлики мне ставили, а попробовали разобраться в описываемой мной парадигме — она значительно упрощает разработку.


Вы в курсе, что реализаций виртуального скроллинга великое множество?))

$mol_list — ни разу не виртуальный скроллинг. Но виртуальный скроллинг у нас тоже есть.

Остановлю ваш спор пожалуй, в React 16 вошли далеко не все фишки Fiber. Что-бы обеспечить обратную совместимость и плавный переход с React 15.x некоторый функционал пока опустили, и об этом прямо написано в статье с информацией об изменениях в 16 версии на сайте React. А вот данный пример с треугольником в Facebook показывали на девелоперской версии react'а, с включённым на полную катушку Fiber'ом.
А каким образом так вышло, что mol-jsx работает вдвое быстрее, чем mol, и требует меньше памяти?

Таким, что реализация бенчмарка на $mol_dom_jsx была сломана и ничего не рендерила. Починил.


Я думал, что mol-jsx — что-то вроде обертки над чистым mol, логично, что он должен работать медленнее?

Нет, к $mol компонентам $mol_dom_jsx отношения не имеет. Это просто рендеринг всего дома через JSX без Реакта, не более.

> Таким, что реализация бенчмарка на $mol_dom_jsx была сломана и ничего не рендерила. Починил.

И оно сразу стало на уровне нейтив-дома. И ангуляр, и реакт — тоже на том же уровне, и только мол каким-то образом в n раз быстрее. При этом с увеличением количества нод затраты на обновление не растут. Выглядит откровенно странно.

При ленивом рендеринге затраты пропорциональны не объёму данных, а размеру экрана.

> При ленивом рендеринге затраты пропорциональны не объёму данных, а размеру экрана.

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

Ну и ссылку на исходники всех этих тестов было бы неплохо иметь.
Допустим, какой-то сложный грид, и там на экране десять тыщ таких вот блоков, они все видны.

Видимых блоков будет не больше 1000. Ну вот для примера такой грид — всего 700 дом-элементов на моём фулхд экране.


Ну и ссылку на исходники всех этих тестов было бы неплохо иметь.

Тут все ссылки.

> Видимых блоков будет не больше 1000.

С чего бы это?

> Ну вот для примера такой грид — всего 700 дом-элементов на моём фулхд экране.

Так там по блоку на ячейку, а их легко может быть и десяток, и полсотни. Умножаете 700 на 10 — получаете 7к. Как ведет себя $mol с 7к видимыми элементами?

Ячейка с 10 блоками внутри и занимать будет в 10 раз больше места, а значит таких ячеек влезет в 10 раз меньше. Число видимых блоков пропорционально площади экрана.

А если эти блоки в ячейке — это слои, один выводится поверх другого?

Нафантазировать можно много чего. Давайте ближе к реальности — в каком приложении требуется 10к блоков в видимой области?

В голову приходит что-то из разряда ненормального программирования: работа с DOM как с пикселями, графики на DOM-элементах, ASCII-подобные мультики.

Вопрос в другом, почему mol на видимой области медленнее vdom, чему там тормозить, если обновления точечные и все-равно все упирается в DOM?
  1. Не всё упирается в ДОМ. Остальные вычисления тоже имеют свою цену. И обеспечение ленивости в том числе.
  2. В зависимости от бенчмарка может выдать меньше попугайчиков, а может больше.
  3. Не тормозит. Цель $mol — 200мс в худшем случае, а не 10мс в лучшем.

Ну, давайте более конкретно..


ToDoMVC, 30 задач, отзумили страницу, чтобы все влезали (загрузка, создание, удаление):


JavaScript Vanilla JavaScript   390 ms  258 ms  63 ms
$mol    393 ms  297 ms  116 ms
Knockout.js     445 ms  367 ms  137 ms
Polymer     965 ms  375 ms  381 ms
AngularJS Angular2  1692 ms 409 ms  224 ms
AngularJS   1082 ms 678 ms  264 ms
React React + Alt   722 ms  748 ms  386 ms
Vue.js  498 ms  918 ms  843 ms
Вы не думали сделать эту фичу отключаемой? Кому очень нужен Ctrl-F, для бенчмарков и для споров.

Отстали бы те, кто использует аргумент ленивости против mol.

Т.к. я использовал атомы (основу mol) вместе с реактом и делал бенчмарки, то думаю, что mol все-равно бы в среднем чуть обгонял популярные фреймворки за счет легковесности.

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

Я думаю не стоит усложнять интерфейс и реализацию ради столь маргинальных кейсов. Отключить ленивость-то не сложно (установкой minimal_height=0 для всех блоков или отказом от $mol_list), но код получится не идеоматичным. Всё же бенчмарки — это не просто конкурс "кто из эквивалентных реализаций наберёт больше попугайчиков", а способ оценить отзывчивость приложения в различных архитектурах и различных ситуациях по умолчанию и объём трудозатрат, чтобы эта отзывчивость не деградировала. В реальном приложении же никто в здравом уме не будет отключать ленивость — почему это надо делать для бенчмарков, ещё более отдаляя их от реальности?


Ещё показательный пример — бенчмарк вывода графиков. $mol мало того, что выводит всё через один элемент path, а не кучу circle, так ещё и точки выводит не все, схлопывая в одну те, что располагаются слишком близко. Не честно? На Highcharts тоже можно сделать просеивание? Можно, только делать это придётся вручную и скорее всего уже после того, как пользователи достанут саппорт своими жалобами по поводу тормозов, когда они загружают в систему слишком много данных.


Так что я против того, чтобы уравнивать все реализации по самой глупой из них ради никому не нужной "справедливости". Главное — какой видимый результат для пользователя, а не сколько пустой работы было проделано.

На мой взгляд, изолированные бенчмарки алгоритмов тоже нужны. Тут дело в нестандартных условиях, которые нельзя предугадать. Например, что такое видимая область, это сколько?

А если стена из мониторов, с очень большим суммарным разрешением?

А если это приложение на каком-нибудь electron или phantomjs, задача которого срендерить много-много страниц pdf? Хотя тут наверное SSR поможет, а он у вас без такой оптимизации.

Как будет масштабироваться mol в сравнении с конкурентами на нестандартные решения? Хоть это сейчас и редкие случаи, но интересный был бы задел на будущее.

Ну так, если стена из мониторов, то и результаты на тех же бенчмарках будут другие. Взял бенчмарк — протестил в своих условиях — что может быть проще? :-) Пытаться экстраполировать данные полученные на маленьком экране на большой — гиблая затея. Например, даже в том же ToDoMVC при увеличении размеров экрана начинает всё сильнее сказываться repaint, который от фреймворка мало зависит.

> ToDoMVC, 30 задач, отзумили страницу, чтобы все влезали (загрузка, создание, удаление):

Во-первых, мол некорректно обрабатывает этот бенчмарк (не обновляются todo-счетчики), во-вторых — непонятно, чего вы там назапускали, т.к. вот:
i.imgur.com/vea9fYe.png
мол некорректно обрабатывает этот бенчмарк (не обновляются todo-счетчики)

Какие ещё счётчики? Если речь про число открытых задач в подвале, то оно обновляется.


непонятно, чего вы там назапускали

Я в ФФ запускал под Убунтой, Хрома у меня сейчас нет под рукой.

> Какие ещё счётчики?

В остальных версиях бенчмарка стоит обновляющийся счетчик на _каждой_ задаче.

Что за счётчики-то? Можно скриншот?

мол:
i.imgur.com/8Q8Ie8t.png
реакт:
i.imgur.com/fp83cgX.png
ангуляр и другие фреймворки аналогично реакту — на каждом todo по счетчику
кстати, выставлены ли в реакте/ангуляре ключи?
ну и еще, там с одной и той же стороны блоки удаляются?

Так вот оно что. У вас просто экран маленький и название задач обрезается. Отзумьтесь и увидите названия целиком.


Ключи выставлены, можете сами посмотреть.


Блоки удаляются посредством клика по кнопке удаления самой первой задачи.

> Так вот оно что. У вас просто экран маленький и название задач обрезается. Отзумьтесь и увидите названия целиком.

Да, действительно, извиняюсь.

> Ключи выставлены, можете сами посмотреть.

<li *ngFor="#todo of todoStore.todos" [class.completed]="todo.completed" [class.editing]="todo.editing">

и где trackBy?

Ну вы бы хоть идентификатор реализации сказали.

> Ячейка с 10 блоками внутри и занимать будет в 10 раз больше места

Нет, не будет. Просто в реальности она будет не пустая, а с контентом, а контент на 10 блоков — ну это даже не много.

Сможете уместить 10 блоков в полторы тысячи квадратных пикселей? :-)

UFO just landed and posted this here

Тогда придётся ещё и про devtools забыть и пилить свой, и браузерные оптимизации рендеринга тоже придётся вручную реализовывать. Зато можно в 3d рендерить, да :-)

Возможно с помощью реакций, того же autorun. Другое дело насколько сложно это будет писать и, главное, поддерживать.

Вы можете использовать любой шаблонизатор, который умеет патчить дом. Да хоть даже JSX с соответствующей реализацией:


class MyHello extends MyComponent {
    @observable name = 'Anonymous'
    @computed render() { return [
        <div id={ this } className="my-hello" >
            Hello, 
            <strong id={ this + '.name' } >
                { this.name }
            </strong>
            !
        </div>
    ) ]
}

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

Вы меня простите за настойчивость, но я реально хочу понять, но не могу. В Вашем примере, как я понял, акцент ставится на то, что если будет множество компонентов MyHello, то при изменении одного, обновится только он, а не все множество. Я прав? Если да, то смотрите. Есть множество айтемов листа и есть лист, который будет принимать массив с данными и создавать компоненты, которые будут похожи на MyHello. И вот один элемент массива с данными удалится и лист запустит пересборку своих детей, которые хранятся в массиве MyHello[ ]. Но данных будет меньше чем айтемов в массиве с детьми. А может наоборот больше, откуда он это знает? поэтому нужно будет писать условия, проверки на больше и на меньше. А что если некоторые элементы массив удалятся из него, а затем добавятся новые и тем самым массив будет иметь длину больше чем в предыдущий раз. Как Вы с этим поступите? Будите писать парсер с родни реактовскому?

Формируете новый список dom-узлов, а специальная функция изменяет дом-узел,
чтобы список дом узлов оказался таким, каким надо. Простейшая реализация такого патча дома занимает всего 50 строк.


class MyHelloList extends MyComponent {

    @observable helloList = [
        new MyHello({ id : this + '.hello=first' )} ,
        new MyHello({ id : this + '.hello=second' })
    ]

    @computed render() { return [
        <div id={ this } className="my-hello-list" >
            { ... this.helloList.map( item => item.render() ) }
        </div>
    ) ]

}
Только здесь есть проблема: поскольку render — обычная ф-я, то на каждое изменение у вас будет перерендериваться весь дом, потому что вычислить ф-ю «частично» — нельзя, а апдейт внутреннего computed инициирует пересчет всех computed вверх по дереву зависимости. А дом, как известно, тормозит, так что либо вы сделаете промежуточное представление с быстрым диффом (как в реакте), либо промежуточное представление с точечными апдейтами и чендж детекшеном (как в ангуляре), либо заизолируете все за нцатью слоями абстракции (как это, кажется, сделано в вашем фреймворке).
обычная ф-я, то на каждое изменение у вас будет перерендериваться весь дом, потому что вычислить ф-ю «частично» — нельзя

Не будет. Функция будет перезапускаться, но то, что она вызывает, не обязательно будет создавать новые дом-узлы. Реализация по ссылке будет реиспользовать существующие узлы, например.


апдейт внутреннего computed инициирует пересчет всех computed вверх по дереву зависимости.

Нет, не инициирует. В том и основное отличие от собственно Реакта, которому необходим пересчёт всех функций выше для того, чтобы увидеть изменения в глубине.


либо заизолируете все за нцатью слоями абстракции (как это, кажется, сделано в вашем фреймворке).

Да нет, там тривиальная реализация — обычный дифф с реальным состояним узла:


render() {
    $mol_dom_render_children( this.dom_node , this.child_nodes )
    $mol_dom_render_attributes( this.dom_node , this.attributes_dictionary )
    $mol_dom_render_styles( this.dom_node , this.style_dictionary )
    $mol_dom_render_fields( this.dom_node , this.field_dictionary )
}
Не будет. Функция будет перезапускаться, но то, что она вызывает, не обязательно будет создавать новые дом-узлы. Реализация по ссылке будет реиспользовать существующие узлы, например.

Ну и чем это все в таком случае отличается от vdom реакта?

Отсутствием vdom? Отсутствием реконциляции? Отсутствием лишних пересчётов? Отсутствием полного ререндеринга при переносе в другого родителя?

> Не будет. Функция будет перезапускаться, но то, что она вызывает, не обязательно будет создавать новые дом-узлы. Реализация по ссылке будет реиспользовать существующие узлы, например.

Сестринские узлы будут, конечно, закешированы, но все узлы вверх будут перестроены.

> Нет, не инициирует. В том и основное отличие от собственно Реакта, которому необходим пересчёт всех функций выше для того, чтобы увидеть изменения в глубине.

@computed будут пересчитаны
Сестринские узлы будут, конечно, закешированы, но все узлы вверх будут перестроены.

Не будут.


@computed будут пересчитаны

@computed пересчитывается только при изменении зависимостей. Если зависимости не меняются — @computed не пересчитывается.

> @computed пересчитывается только при изменении зависимостей.

Любая внутренняя нода — зависимость (внутренний @computed). Если какая-то внутренняя нода поменялась, то весь дом вверх следует пересчитать.

Не весь, а лишь вычислить содержимое ноды уровнем выше. И только если нода поменялась, что зачастую не так (привет реиспользование существующих нод).

> Не весь, а лишь вычислить содержимое ноды уровнем выше.

Которая, в свою очередь, является @computed значением для своего предка, тот — @computed для своего, и так до корня.

> И только если нода поменялась, что зачастую не так (привет реиспользование существующих нод).

В рассматриваемом примере @computed render() ВСЕГДА возвращает другую ноду, если какая-то зависимость изменилась. Чтобы этого избежать — вам придется заменить простую ф-ю render на некую хитрую сущность, которая будет сама менеджить ноды определенным образом при помощи некоей внутренней магии. Короче — получится то, что выдает компилятор ангуляра из темплейтов :)
Давайте по порядку, вот ваш пример:
@computed render() { return [
        <div id={ this } className="my-hello-list" >
            { ... this.helloList.map( item => item.render() ) }
        </div>
    ) ]

в реакте взыов (div) создает новую ноду в вдоме. Вы говорите, что у вас оно не создает новую ноду, а берет уже существующую (как он кстати, ее находит?), потом проходит алгоритмом чендж детекшена и меняет только те ноды, которые изменились, так? Каким образом он определяет какой реальной ноде дома какой терм кода соответствует, если это невозможно сделать для тьюринг-полного языка (каковым является jsx) без исполнения самого кода?
в реакте взыов (div) создает новую ноду в вдоме.

И при чём тут Реакт?


Вы говорите, что у вас оно не создает новую ноду, а берет уже существующую (как он кстати, ее находит?)

По идентификатору.


Каким образом он определяет какой реальной ноде дома какой терм кода соответствует

Никак не определяет. Упомянутый код:


        <div id={ this } className="my-hello" >
            Hello, 
            <strong id={ this + '.name' } >
                { this.name }
            </strong>
            !
        </div>

Транслируется компилятором в:


$mol_dom_jsx( 'div' , { id : this , className : "my-hello" } ,
  "Hello" , 
 $mol_dom_jsx( 'strong' , { id : this + '.name' } , this.name ) ,
 '!' ,
)
> По идентификатору.

Отлично, теперь давайте проверим, насколько я верно все понял.

1. Если идшники не указать, то все ноды будут пересоздаваться, верно?

2. Допустим, был дом (div id=1 class=foo (div id=2 class=bar)) и мы рендерим render() = (div id=1 class=bar (div id=2 class=foo)). В данном случае никто никаких нод пересоздавать не будет, а просто у существующих нод поменяются class, так?

3. Допустим был дом тот же, что и раньше, (div id=1 class=foo (div id=2 class=bar)), мы рендерим render() = (div id=2 class=bar (div id=1 class=foo)), что вообще произойдет?

4. Что будет если я отрендерю несколько нод с одним ид? То есть, усложним предыдущий вариант — тот же оригинальный дом, но ф-я (div id=2 class=bar (div id=1 class=foo (div id=2 class=yoba)))
  1. Да
  2. Да.
  3. Ноды поменяются местами.
  4. Один идентификатор — одна нода. В вашем примере mobx упадёт с циклической зависимостью.
> Ноды поменяются местами.

Что это значит? Какой дом в результате получится и какая нода будет какому терму ставиться в соответствие? И получается что семантика JSX тут полностью сломана, т.к. результирующий дом не зависит от ф-и render? Зачем вообще тогда ф-я render, jsx и остальное, если оно ничего не делает?

> Один идентификатор — одна нода.

А если их будет все-таки две, например, в разных сестринских элементах?

Какой дом написали — такой и получится. Что не понятно?


render() {
    if( this.twoInOne ) {
        return (
            <div id="1" class="foo">
                <div id="2" class="bar" />
            </div>
        )
    } else {
        return (
            <div id="2" class="bar">
                <div id="1" class="foo" />
            </div>
        )
    }
}

При переключении twoInOne будет пара insertBefore и всё.


Ссылаться идентификатором вы можете на одну ноду из разных мест.

> Что не понятно?

Вы выше сказали, что дом строится на основе существующего, а не на основе того, что написано в рендере. Вот я и пытаюсь понять, как это все увязывается.

> При переключении twoInOne будет пара insertBefore и всё.

Вы, пожалуйста, ответьте на вопрос из позапредыдущего поста. Какой дом получится в пункте три, и почему именно такой? И зачем нужна ф-я рендер, если структура итогового дома никак не связана с ее jsx-описание в рендер?

> Ссылаться идентификатором вы можете на одну ноду из разных мест.

То есть если я вызову render() = (div id=1 class=foo), и у меня есть десяток нод с ид=1, то они все 10 обновятся, при том не важно когда и как вызвана данная render, верно?
Вы выше сказали, что дом строится на основе существующего, а не на основе того, что написано в рендере.

Берётся существующий и изменяется до написанного.


То есть если я вызову render() = (div id=1 class=foo), и у меня есть десяток нод с ид=1, то они все 10 обновятся

Если вы рендерите через предложенный JSX адаптер, то у вас не может получиться в реальном доме несколько нод с одним идентификатором. Если вы таких нод надобавляли другими путями, то JSX возьмёт первую из них. Вы правда не в состоянии прочитать простейший JS код? Или принципиально не ходите по ссылкам? Ну давайте я вам сюда выгружу:


export function $mol_dom_make( id? : string , localName = 'span' ) {
    const document = $mol_dom_context.document

    let node = id && document.getElementById( id ) as Element
    if( !node ) {
        node = document.createElement( localName )
        if( id ) node.id = id
    }

    return node
}

export function $mol_dom_jsx(
    Elem ,
    props  ,
    ...children 
) {
    let node
    if( typeof Elem === 'string' ) {

        node = $mol_dom_make( props && props['id'] , Elem )
        $mol_dom_render_children( node , [].concat.apply( [] , children ) )
        $mol_dom_render_fields( node , props )

    } else if( typeof Elem === 'function' ) {

        node = new ( Elem as any )({ childNodes : children , ... props })
        if( node.render ) node = node.render()

    }

    return node
}
Так у вас получилось как в реакте, только криво (там идшники нужны только для менеджмента нод в списке, остальные нормально диффаются и не пересоздаются сами по себе). То есть, по сути, переложили задачу диффа на плечи программиста + проблемы с повторными и кривовыставленными идшниками.
остальные нормально диффаются и не пересоздаются сами по себе

Ага, конечно. Изменение родителя приводит к полному пересозданию поддерева.


То есть, по сути, переложили задачу диффа на плечи программиста

В каком это месте?


проблемы с повторными и кривовыставленными идшниками

Так не ставьте кривые айдишники.

> Ага, конечно. Изменение родителя приводит к полному пересозданию поддерева.

Только в вдом, операции на котором быстрые. В реальном дом — не приводит.

> В каком это месте?

Как в каком? Надо проставлять идшники, фиксируя принадлежность нод.

> Так не ставьте кривые айдишники.

Хорошая история, но вообще фреймворк должен следить за такими вещами.
Только в вдом, операции на котором быстрые. В реальном дом — не приводит.

Приводит.


Надо проставлять идшники, фиксируя принадлежность нод.

Это не "задача диффа", а "задача идентификации", которую никакой фреймворк за программиста не сделает. Вернее он может попытаться угадать, как это делает Реакт, и неизбежно угадать неправильно, как в примере по ссылке выше.

Она не пропадает и появляется, а переносится из одного родителя в другого. При этом дом в обоих случаях одинаковый. Присмотритесь по внимательнее.

DOM — одинаковый, но VDOM — разный. Разные компоненты в реакте намеренно были сделаны неэквивалентными друг другу.
Другими словами, Вы ставите вопрос так — зачем этот неправильный реакт, когда есть мой правильный mol. Я правильно я Вас понял? Иначе Вы предлагаете каждому создавать свою систему рендера, которая ну никак не может с помощью 50 строчек кода покрыть все потребности, также как и предугадать их все. Никому не хочется начиная проект, понять, что его систему рендера, именно сейчас, нужно дорабатывать.

Что сказать, сделайте свой mol популярней реакта и им будут пользоваться, а пока я не видел вакансии $mol девелоперов. Да и знак $ смотрится просто ужасно.
Другими словами, Вы ставите вопрос так — зачем этот неправильный реакт, когда есть мой правильный mol. Я правильно я Вас понял?

Нет, $mol_view, конечно, построен по тому же принципу, но сейчас речь не о нём. Ренедерить вы можете любой библиотекой, умеющей применять изменения к дому. $mol_dom_jsx — пример такой библиотеки, написанной на коленке за пол часа.


Иначе Вы предлагаете каждому создавать свою систему рендера, которая ну никак не может с помощью 50 строчек кода покрыть все потребности, также как и предугадать их все

Да их кот наплакал, этих потребностей:


  1. Задать список дочерних dom-узлов
  2. Задать словарь атрибутов
  3. Задать словарь dom-свойств
  4. Задать словарь обработчиков событий

Какие ещё у вас могут появиться потребности?


Что сказать, сделайте свой mol популярней реакта и им будут пользоваться

Что раньше появилось: курица или яйцо?


а пока я не видел вакансии $mol девелоперов.

У нас открыта вакансия в Питере. Приходите, научим.


Да и знак $ смотрится просто ужасно.

Зарплата будет в рублях, не волнуйтесь. :-)

Как раз с MobX можно писать рендереры, которые точно будут знать, что изменилось.Например, получать два массива, старый и новый, при добавлении/удалении.
А ведь ещё server side render. Его тоже придется самому делать…

Зачем там что-то самому делать, если jsdom уже давно написан за вас?

> Остаётся только один вопрос: если у вас есть информация о непосредственных зависимостях любого состояния, а значит вы точно знаете каке узлы в дереве нуждаются в перерисовке

Для этого надо само дерево создавать в рамках того же подхода, то есть задать его как @computed render и менеджить через MobX. Но тогда придется прибить гвоздями слой рендера к слою менеджера состояний.

Я бы не выделял "менеджмент состояний" в какой-то отдельный слой. Это по определению инфраструтурное средство для реализации коммуникации. Нет ничего плохого в том, что все слои будут коммуницировать единообразною

> Я бы не выделял «менеджмент состояний» в какой-то отдельный слой.

Я бы тоже не стал, мы этот вопрос уже, к слову, обсуждали, но так уж исторически слоилось что сам дом (либо какие-то его представления типа вдома) не включается в стейт.
По какому определению? Коммуникации между чем и чем?

Коммуникация между зависимыми состояниями. Мы же про MobX говорим.

Покажите мне живой пример, в песочнице, с @computed, где бы я в getter мог поставить console.log и она бы выполнилась только при первом обращении.
Пока выше товарищ Дмитрий Карловский продолжает рекламировать свой $jin $mol, мы вернёмся к обсуждению статьи. Mobx-state-tree — это действительно шаг вперёд для MobX, получилось избавиться от некоторых минусов MobX.

Хорошим дополнением к статье будет запись доклада от Michel Weststrate (автора MobX) на ReactEurope@2017.

Или его же недавняя статья (28 sep 2017), в ней в т.ч. есть упоминание об одном из минусов MobX.

Очень жаль, что вы увидели в моих словах лишь то, чего в них не было, и совсем не заметили того, о чём шла речь. (Подсказка: при использовании mobx или любой другой реализации ОРП, Реакт или любая другая библиотека виртуального дома — как собаке пятая нога).

Если вы имели ввиду своё мнение о самодостаточности $mol и бесполезности других библиотек, то заметил. К сожалению (или к моему счастью?), пока что точка зрения mayorovp и Druu мне больше импонирует.
Если вы имели ввиду

Я вроде бы вполне доступным языком написал, что имею ввиду:


при использовании mobx или любой другой реализации ОРП, Реакт или любая другая библиотека виртуального дома — как собаке пятая нога

Где вы тут увидели $mol?


К сожалению (или к моему счастью?), пока что точка зрения mayorovp и Druu мне больше импонирует.

Каждый человек имеет некоторый горизонт взглядов. Когда он сужается и становится бесконечно малым, то превращается в точку. Тогда человек говорит: «Это моя точка зрения». (с) Давид Гильберт

Увидел в процитированных вами ваших же словах. Там ссылка на вашу же статью с рекламой вашей библиотеки. Да и последующие десятки сообщений в обсуждении текущей статье говорят об этом. Почему-то всё замыкается всегда на вашу библиотеку :)

Ай не хорошо как на личности переходить :) Попробуйте найти другие пути, как рассказать про вашу крутую библиотеку.
Там ссылка на вашу же статью с рекламой вашей библиотеки.

Статья про парадигму программирования. Примеры кода приведены с использованием моей библиотеки, так как только она поддерживает все упоминаемые фичи парадигмы. Ну а ссылку я дал на конкретную часть выступления, посвящённую бессмысленности и беспощадности архитектуры React-а.


Почему-то всё замыкается всегда на вашу библиотеку

Не я увёл обсуждение в сторону $mol. Впрочем, $mol во всю использует упомянутые в статье принципы, хоть и без mobx.


Ай не хорошо как на личности переходить :)

Это лишь предложение расширить кругозор, а не цепляться за привычную точку зрения.


Попробуйте найти другие пути, как рассказать про вашу крутую библиотеку.

А давайте побрейнштормим. Вот есть инструмент. Позволяет писать мало, а делать много. Масштабируемая архитектура. Стоимость использования нулевая. Поддержка бесплатная и оперативная. Как о нём рассказать, чтобы у него появилось хотя бы 100 звёзд на гитхабе?

1. Слишком много новых концепций. Например, может лучше начать с mol_jsx, вместо tree, т.к. многие не хотят это постигать целиком
2. Нужны примеры в сравнении с аналогичными на react
3. Статьи на англоязычные ресурсы
4. Посмотреть на опыт авторов mobx, catberry, авторы которых с нуля и быстрее вас получили намного больше звезд

А в реальности, мне кажется, что только много денег, вложенные в маркетинг, могут помочь, у меня то всего 20 звезд на гитхабе, около 100 у вас это еще неплохо =)
Например, может лучше начать с mol_jsx, вместо tree

Тогда потеряется львиная доля преимуществ и получится шило на мыло.


Посмотреть на опыт авторов mobx, catberry

А где на него посмотреть?

Тогда потеряется львиная доля преимуществ и получится шило на мыло.
Что-то потеряется, к сожалению. Мое предположение в том, что из-за конкуренции в вебе и js, вам без огромных вливаний ресурсов, как у гугла в ангулар, не продать огромный кусок чего-то сильно отличающегося.
У всех космические корабли похожи на энтерпрайс, у вас на инопланетную губку из водорослей, где все замкнуто на вас:
1. Свои соглашения по импортам и связянного с ними стилем кодирования
2. Необычная реализация инверсии контроля через контексты
3. Своя экосистема: пакетный менеджер и сборщик зависимостей (mam)
4. Своя библиотека для DOM, http и пр. утиль
5. Своя библиотека работы со стейтом (mol_atom), с удачной, но непривычной идеей в основе
6. Иерархическое наследование на tree, вместо плохо кастомизируемой, но простой и понятной всем композиции на шаблонах, таких как JSX

Это все может быть 100 раз оправданно, цельно и логично и опережать время лет на 5, но оно просто ни на что не похоже. Аналогии не работают, т.к. это новый подход к разработке в целом. Может попробовать продать маленький кусок, встроив в привычную экосистему, как автор mobx?

А где на него посмотреть?
Мне б тоже хотелось знать, может просто их спросить? Я помню как автор mobx много где упоминал свое решение пару лет назад, тогда еще mobservable, например как здесь и у него было пару десятков звезд, а сейчас 11К.
Не люблю, когда в статьях рассматривают только положительные стороны и не затрагивают недостатки, поэтому подниму тему недостатков.

Когда я попробовал использовать Mobx, мне показалось, что на нем писать надо очень осторожно, иначе если упустишь что-нибудь, то придется долго искать ошибку.
Этому способствовали следующие особенности:
  • Чтобы не было лишних перерисовок, все изменения лучше писать в функциях, используя декоратор action, или использовать runInAction.
  • Объект желательно делать полностью наблюдаемым. В противном случае вложенные объекты не будут вызывать оповещение об изменениях.
    Из-за этого при присваивании нового объекта нужно не забывать писать что-то вроде: parentObj.level1Obj = observable({level2Obj: {propA: 10}});
  • Извиняюсь, но тут я уже не помню. Суть примерно следующая:
    в render надо указывать полный путь (либо использовать @computed) для перерисовки при изменении какого-нибудь используемого свойства.
    Например, в render писать так:
        render() {
            return <div>{'Username: ' +  Stores.users.selected.name}</div>;
        }

    Если же в какой-нибудь переменной (вроде вне render) сохранить Stores.users, а потом написать
    return <div>{'Username: ' +  tempUsers.selected.name}</div>;
    
    то обновления не будет.


В общем, есть такие тонкости, которые усложняют разработку — то надо не забыть прописать, другое не забыть, третье не забыть. Может я где-то неправ, все-таки пользовался Mobx давно и не долго.
Из-за этого при присваивании нового объекта нужно не забывать писать что-то вроде: parentObj.level1Obj = observable({level2Obj: {propA: 10}});

Если parentObj.level1Obj — наблюдаемое свойство, то так как вы написали делать не нужно. Объекты без прототипов рекурсивно превращаются в наблюдаемые автомагически.


Если же в какой-нибудь переменной (вроде вне render) сохранить Stores.users ...

… то изменение свойства users не будут наблюдаться, что очевидно. Изменения вложенных свойств наблюдаться не перестанут.

Спасибо за доклад, было довольно увлекательно, но остались некие вопросы. Вы говорите, что mobx-state-tree работает с данными аналогично тому, как react работает с компонентами, но я не совсем понял сферу его применения. Если в компонентах мы не используем getSnapshot и onSnapshot, а используем observable из modx, то mobx-state-tree нужен лишь для time traveling?

Еще показалось интересным, что в начале доклада Вы показываете кусочек кода с ручной подпиской на изменения через state.on('change'), а в конце доклада мы видим весьма похожую функцию onSnapshot, которая уже такой плохой не считается. Поясните и ее предназначение, пожалуйста.

Также как-то немного выпало из доклада, вот этот хелпер types — это часть modx?
Также как-то немного выпало из доклада, вот этот хелпер types — это часть modx?

Хелпер types — это инфраструктура mobx-state-tree. Просто mobx использует ES6 классы а не хелперы.


Еще показалось интересным, что в начале доклада Вы показываете кусочек кода с ручной подпиской на изменения через state.on('change'), а в конце доклада мы видим весьма похожую функцию onSnapshot, которая уже такой плохой не считается. Поясните и ее предназначение, пожалуйста.

Так плоха же не сама по себе подписка на событие, а тот факт, что их много, а еще дублируется render.


onSnapshot же дергается при любом изменении модели, сюда "слетаются" любые события. Кроме того, на onSnapshot никто не дергает render, это событие не для того сделано.


PS обратите внимание, в слове "MobX" третья буква — b а не d. ModX — это совсем другой проект.

Из ответа не очень стало понятно зачем все-таки mobx-state-tree, если мы можем напрямую использовать mobx, описывая состояние приложения через ES6-классы. И как непосредственно кроме отладки или логгинга используется onSnapshot? Или это и есть место для всякого подобного middleware кода?

PS обратите внимание, в слове «MobX» третья буква — b а не d. ModX — это совсем другой проект.

Увидел опечатку уже после отправки, когда нельзя было редактировать пост. А вроде бы просто налил себе чай, отвлекся буквально на минуту, эх, Хабрахабр. Имел дело и с этим другим проектом, видимо, опечатался.

Одной отладки достаточно чтобы onSnapshot был нужной фичей. Правда, лично я скорее буду собирать снимок вручную чем вот так переносить код с ES6 классов на какие-то хелперы...

А не подскажете, кто в теме, нормально ли работает MobX с React 16?
Я имею в виду Fiber Architecture, асинхронный рендеринг и вот это вот все.

Ну, MobX вообще не завязан на React технически — его можно хоть с Angular использовать или еще чем-нибудь. Если речь про официальный байндинг mobx-react — то он официально поддерживает React 16. Да ну и там нечему особо сломаться-то.

Sign up to leave a comment.