Pull to refresh

Comments 169

Virtual DOM в React является медленным и неточным

В движке Fiber проблема производительности уже неактуальна


React требует сложного асинхронного программирования при общении с сервером

Напротив, в связке с Redux весь код бизнес-логики на клиенте может быть сведен в единое место, представляемое одним или несколькими комбинированными reducer-ами. Продолжительные по времени действия легко описать в sagas, а еще лучше реализовать всю архитектуру через events — в таком случае явные асинхронные вызовы вообще не нужны


Даже используя propType React сможет найти ошибки только во время работы программы, а не во время компиляции.

Если нужна статическая проверка, можно использовать Flow


Поддерживает ли Binding.scala серверный rendering?

Если нужна статическая проверка, можно использовать Flow
А лучше вообще на TypeScript перейти
<sarcasm>Потому что он не тайп-чекер.<\sarcasm>
Продолжительные по времени действия… еще лучше реализовать всю архитектуру через events — в таком случае явные асинхронные вызовы вообще не нужны

Куда можно сходить посмотреть примеры?

Если нужна статическая проверка, можно использовать Flow

Костыли в виде Flow не нужны (уже не нужны), существует более полноценное решение в виде TypeScript. Если TypeScript не дружит с React, то это проблема React и сообщества его фанбоев.

Костыли в виде TypeScript не нужны (уже не нужны), существует более полноценное решение в виде Flow. Если Flow не дружит с Angular, то это проблема Angular и сообщества его фанбоев.
Я всегда говорил, что React — говно.
jQuery наше все :)
Так говорят те, кто не хочет разбираться в этой каше из ES6/Фреймворках/сборщика/nodejs/npm/командной строке и остальном. Достаточно просто углубиться и понять, что фронтенд за последние несколько лет ушел далеко вперед, и на сколько отстали люди с jquery.
Вы правы, но например я чем дальше, тем больше себя ловлю на мысли, что вся эта каша технологий — это совершенно непродуктивная трата нашего времени. Особенно в том свете, что задачи эффективной разработки приложений уже когда-то были прекрасно решены на классическом десктопе, и вместо переиспользования опыта идёт набивание шишек, построение костылей, изобретение велосипеда. Да что говорить хотя бы про то, что почти четверть века тому назад мы могли нарисовать форму в дизайнере, сразу увидеть, как она будет выглядеть, забиндить с данными, и тоже всё это увидеть вживую. А сейчас почему-то не можем.
Если речь идет о большом проекте, то сами понимаете, поддерживать его достаточно трудно, если это все в одном файле js. Уже много было статьей, о преимуществах компонентного подхода разработки, так вот, вносить правки в проект, где например react+redux достаточно удобно.
если это все в одном файле js

Бля, ну какой один файл?
Вы без фреймворка на файлы поделить не можете?

Ниасиляторы.
наверно потому что сравнивать подход к разработке веб интерфейса и десктоп интерфейса — это сравнение красного с соленым.
Вы ошибаетесь. Разработка десктопного интерфейса, по сути ничем существенным не отличается от разработки веб-интерфейса. Те же формы, элементы управления, графика. Скажете, нет адаптивной вёрстки и не нужно поддерживать работу на самых разных устройствах? Да щас же. Пользователь может иметь экран 640х480, а может 1920х1080, и может как ему угодно видоизменять размер окна вашего приложения. И ваш интерфейс точно так же должен подстраиваться под различный размер окна. Изначально существенно отличалась парадигма взаимодействия, т.к. десктоп был обычно или монолитным, или с толстым клиентом, веб работал как тонкий клиент, по принципу древних терминалов — заполнил данные/отправил/получил ответ/отобразил. Нынче и эта грань стёрлась.
Поэтому та разница в подходе к разработке веб и десктопного интерфейса не имеет под собой каких-то принципиальных архитектурных обоснований. Только потому что так исторически сложилось. Потому что десктопные технологии разработки приложений изначально росли, как… технологии разработки приложений. А веб-приложения выросли из нехитрого языка разметки, который предназначался для отображения картинок и гипертекста в браузере, и простенького скриптового языка для оживления статических элементов.
И как же интересно вы делаете адаптивность на десктопе? Максимум наверно тянете кнопки и инпуты на всю ширину. В вебе же нужно полностью перекомпоновать интерфейс, если его открывают на мобилке. Оффлайн режим, SEO, непредсказуемость доступных API, непредсказуемые платформы (сорян, упаковать свежий .NET Framework в инсталлятор не получится) и т.д.
И как же интересно вы делаете адаптивность на десктопе?

По-всякому бывает. Иногда тянули на всю ширину, иногда скрывали и перестраивали фреймы. А что особенного в адаптивной вёрстке в вебе нынче? «Полностью перекомпоновать интерфейс» в 95% случаев эквивалентно «выстроить блоки не в ширину, а лентой, а меню убрать под кнопку». Причём на уровне модных библиотек CSS уже всё настроено, только не поленись нужные стили прописать.
Оффлайн режим

Разве это какая-то особенность, которая недоступна десктопным приложениям? Или вы опять путаете историческую костыльность решения этой типовой задачи для веб-приложений с какими-то архитектурными потребностями?
SEO

Да, только SEO лежит далеко-далеко за рамками архитектурных вопросов разработки веб-приложений. От разработчика тут требуется лишь отсутствие ошибок вёрстки, а остальное уже в компетенции контент-менеджера и сеошника.
непредсказуемые платформы

Странно, как у вас поворачивается язык называть «непредсказуемыми платформами» три браузерных движка, которые существуют в нашей эпохе, и которые имеют лишь небольшие отличия в поведении. Вы вот вспомнили бы, сколько головной боли в своё время доставило появление UAC или выноса локальных данных приложений в пользовательский профиль на винде :)
Интересно, что в реальной жизни за первый год использования современных фреймворков и библиотек со всей тянущейся инфраструктурой описанной выше вы будете настраивать эту самую инфраструктуру 2-3 раза, еще будете по мере необходимости ее обновлять и улучшать, а остальное время вы посвятите как раз программированию в современных реалиях.
фронтенд за последние несколько лет ушел далеко вперед

Куда именно он ушел? :)
отстали люди с jquery

В чем же?

Эти все JS-фреймворки в основном не нужны.
Они лишь привносят проблемы и сложности, которые разработчики потом героически решают.

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

Так куда ушел Реакт и в чем отстал jQuery? :)

В разделение ответственностей. В декларативном подходе.

А какое отношение имеет Реакт к Реактивному Программированию кроме похожего названия?

Обеспечивает реакцию в виде перерендеринга в ответ на изменение состояния.

Реакцию обеспечивает любой ивент эмиттер. Реактивное программирование вообще не про реакции и уж тем более не про рендеринг шаблонов, а про гранулированное распространение изменений. Подход вида "при вызове метода создаём новыи мир и сравниваем его с текущим" — резко противоположен реактивному программированию.

"При вызове метода создаём новый мир и сравниваем его с текущим" — незначащая деталь реализации, оптимизация. Реакт обеспечивает распространение изменений состояния на его представления (DOM прежде всего) без вмешательства разработчика. Разработчик только делает изменения состояния с помощью setState, декларативно описывает представление, а Реакт обеспечивает распространение изменений в DOM или подобную сущность. Что под капотом у него две копии виртуального дома и он дергает методы дома на основании разницы между копиями — лишь оптимизация по типу кэширования/мемоизации. Функционально же это реактивное приведение представления в вид, соответствующий состоянию, вычисляемое значение с побочным эффектом.

Нет, если отбросить подкапотную оптимизацию, то внешне работа реакта ничем не отличается от подстановки переменных в html-шаблон. То, что вы вызываете setState(state) вместо renderHTML(data) — сути не меняет. Вы вызвали метод, передали в него данные — получили отрендеренный дом. Это не реактивное программирование, а обычная трансформация. Суть же реактивного программирования в том, что вместо того, чтобы описывать как состояние А влияет на состояния B и C, вы описываете, как состояние А влияет на состояние В и как состояние В влияет на состояние С, и при изменении состояния А меняется и состояние С. Если у вас всего 2 состояния (стейт и дом), то это уже никакое не реактивное программирование.


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

Отличается. С шаблонизатором мы пишем что-то вроде:


function onChange(event) {
  this.state = transofrm(e, this.state); // какая-то трансформция состояния
  this.render(); // подразумевается, что есть что-то вроде this.element, this.template
}

В Реакте я пишу что-то вроде:


function onChange(event) {
  this.setState(transofrm(e, this.state)); // какая-то трансформция состояния
}

и всё. this.render() не вызывается напрямую, а дергается системой как реакция на изменение состояния, как процесс распространения зависимых от этого состояния вычисляемых значений с побочным эффектом в виде перестроения DOM.


Если у вас всего 2 состояния (стейт и дом), то это уже никакое не реактивное программирование.

Реактивное программирование не зависит от того 2, 3 или более состояний в нём есть.
Его суть в том, что мы декларативно описываем как одно состояние зависит от другого и при изменении последнего получаем изменение первого автоматически, без явного вызова функции вычисления. Сколько таких цепочек, 1, 2, 3 или миллион на концепцию не влияет. В Реакте мы описываем как состояние DOM зависит от состояния компонента и меняя состояние компонента имеем автоматическое распространение изменений состояния компонента на состояние DOM/

То есть если мы заменим...


function onChange(event) {
  this.state = transofrm(e, this.state); // какая-то трансформция состояния
  this.render(); // подразумевается, что есть что-то вроде this.element, this.template
}

… на...


this.onChange(event) {
    this.setState(transofrm(e, this.state)); // какая-то трансформция состояния
}
this.setState(state) {
  this.state = state;
  this.render(); // подразумевается, что есть что-то вроде this.element, this.template
}

… то это вдруг станет Реактивным Программированием? Круто, конечно, но определению не соответствует.

Не станет, setState в Реакте асинхронный, он даже не пост-событие об изменении стейта эмиттирует, а задачу на изменение стейта ставит в очередь.

Ок, добавляем асинхронщину. Теперь станет?


this.onChange(event) {
    this.setState(transofrm(e, this.state)); // какая-то трансформция состояния
}
this.setState(state) {
  this.state = state;
  requestAnimationFrame( this::render ); // подразумевается, что есть что-то вроде this.element, this.template
}

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

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


Давайте я продолжу:


Когда мы все процедуры помещаем в один глобальный объект — это ООП.
Когда в проекте есть хотябы одна чистая функция — это ФП.
Когда все процедуры пишутся в АСТ — это ДП.

Правильно продолжили в каком-то приближении.

Реакт к Реактивному Программированию не имеет никакого отношения? :)

Тогда давайте вернемся к корневому комменту:
Я всегда говорил, что React — говно.
jQuery наше все :)


Просьба рассуждать в контексте Реакт против jQuery, не отклоняться от темы. :)

Я отвечал на совершенно другой тезис:


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

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

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


Но хороший фреймворк берёт на себя львиную долю рутины.

Немножко не в ту сторону
jQueryAngular 2 наше все :)
Fixed.
Angular 2 наше все :)

Angular 4 версии уже.
Я и не думал, что на хабре так много ниасиляторов jQuery и людей без стержня, которым вбухали в голову React.
Вы знаете, я очень хорошо помню, как в 2012 году на jQuery, и нескольких плагинах делал не очень-то уж и сложный фронтенд одного портала. Так вот, вспоминая это, честно говоря, хочется повеситься. Стержень вроде на месте. jQuery осилил еще в 2010 году. Так вот я к чему, безусловно есть класс задач, для решения которых не требуется Angular/React/Ваша-любимая-библиотека-или-фреймворк, однако это вовсе не означает, что фреймворки не нужны, Вы же, как мне кажется, рассуждаете по принципу «Мне не нужно, значит никому не нужно, а кому нужно, тот просто мне завидует», к слову, есть огромное количество задач, где не нужен и jQuery, это же не заставляет Вас говорить, что он не нужен. В связи с последним, можно перефразировать Ваш комментарий, заменив jQuery на VanillaJS, а React на jQuery.

Не то что я особый поклонник React и ненавистник Scala, но не является ли это очередным случаем "я считаюсь fullstack, но ниасилил React и вообще фронт не люблю"?


что приложение на фреймворке Binding.scala содержит всего 154 строки кода

А сколько весит scala.js (имеется ввиду скаловский runtime)? Года три назад было 20 мегов.

Уже прогресс у ребят. (src)
It's disappointing that we reach only 2.7 MB this way, but it's definitely better than 16.


Прогресс заметный, но все равно. Должны быть очень веские причины тащить в проект 2.7 мега одног только рантайма.

Вы это серьезно? Сравниваете неминифицированный react с минифицированной scalajs + react? Минифицированный react весит 80kb.

В варианте скалы тоже не всё минифицировано. Да и сравниваю я зазипованные объёмы. В любом случае далеко не несколько мегабайт. К слову сказать, вариант на неминифицированном $mol весит вообще 50кб.

И ведь действительно, при чём?

Сколько же в тексте желтухи и некорректных утверждений.

Компоненты React трудно использовать повторно в сложных веб-проектах.


Разрабатываем и поддерживаем интерактивный фронтэнд для большого и сложного проекта. Успешно переиспользуем компоненты. Callback'и есть только в качестве обработчиков событий у дочерних компонент (прямо как в натуральном DOM). Всё очень хорошо выходит.

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

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


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

К сожалению, поддержка HTML в React является неполной. Разработчик должен вручную заменить class на classname, а for на htmlFor.


Автор, кажется, даже не открывал документацию и совсем не понял, что такое JSX и зачем оно нужно. Эх…
А еще очень смешно читать про никзую производительность React, в то время, когда его Virtual DOM порой даже быстрее нативного JS. Молчу про Fiber.
UFO just landed and posted this here
Тут имелись в виду ручные обновления DOM без синхронизации по RAF
UFO just landed and posted this here
Быстрее не доступ к DOM, ибо vdom обращается к нему точно так же. Быстрее общая скорость отрисовки, так как множественные разовые обновления буферизуются по фрейму и применяются разом, не вызывая множественных перерисовок. Ну то есть, быстрее не технология, а техника.
UFO just landed and posted this here
Не подменяйте понятия.
генерируя новый виртуальный DOM на каждый чих, как в публикации и пишется.
А вы тоже не подменяйте.

Эта ветка не про скорость генерации vdom, а про буферизацию доступа.

Нативный JS не отменяет необходимости писать эффективный код.
Точно так же если тупо писать на Реакте «в лоб»
Все верно, но это совсем не про vdom.
UFO just landed and posted this here
множественные разовые обновления буферизуются по фрейму

Это относительно редкий кейс. Чтобы эта буфферизация сработала, необходимо, чтобы событие всплывало чаще 60 раз в секунду. Кроме того, гораздо эффективней не буферизировать обновления ДОМ, а производить пересчёт данных не чаще 60 раз в секунду. Но для этого нужна совершенно другая (ленивая) архитектура, под которую реакт совершенно не приспособлен.

Чтобы эта буфферизация сработала, необходимо, чтобы событие всплывало чаще 60 раз в секунду.
Если этого не происходит, то и прямого доступа в dom достаточно.

Но для этого нужна совершенно другая (ленивая) архитектура, под которую реакт совершенно не приспособлен.
Странно, а где ссылка на $mol?
Повторное использование компонент повлияло как раз на переход к React, это вообще одно из его основных преимуществ

Переход с чего? Преимуществ перед чем? Что-то мне подсказывает, что вы могли бы произвести компонентную декомпозицию никуда не переходя.

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

Вы так и не ответили с чего переходили.


Приведёте пример такого независимого блока?

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

Всё зависит от Вашей специфики. Мы торгуем. У нас есть элементы, вроде корзины покупателя или формы оплаты, которые можно встраивать в разные контексты и получать единое настраиваемое представление. Я не знаю, что Вы хотите мне доказать, потому что я не своё мнение описываю, а с чем имею дело в работе. Я нигде не сравнивал React ни с чем, не говорил, что он лучшее решение из имеющихся в индустрии. Только лишь указал, какие его «минусы» по мнению автора вызвали у меня негодование. Часть этих минусов от непрочтения документации автором. А тот, к которому прицепились вы, вообще является преимуществом технологии. Не над чем-то преимуществом, а в целом — то, что записывают в графу «Плюсы».

В общем, если Вы хотите посравнивать и поспорить, я в это влезать не буду, уж простите.

Без примеров кода, ваши заявления довольно голословны. А без кода мне приходится гадать на кофейной гуще:


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

Какой из этих вариантов по вашему можно считать "успешным"?

То есть мы переходим уже к организации бизнес-логики? Для неё у нас Flux. Не Redux, но одна из вариаций реализации концепции. Всё общение с сервером там. Общая логика приложения в хранилищах. Взаимодействие с состоянием других компонентов тоже через них. Компоненты имеют и параметры, и внутреннее состояние. Компоненты нужны для визуализации и взаимодействия с пользователем, а для этого нужно в правильных дозах использовать и то, и другое.

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

То есть компоненты не являются самодостаточными. Чтобы где-то воспользоваться "готовой" компонентой необходимо обеспечить её соответствующим набором экшенов, сторов и редьюсеров. И речь не только о "доменной модели", но и о тех состояниях интерфейса, которыми нужно управлять из вне.

Говорили о переиспользовании. Потом притащили бизнес-логику. Теперь «самодостаточность» какая-то появилась, «готовность»… Какой-то спор ради спора.

А какой еще возможен вариант? Почему он более "успешный"? И в чем проблема его реализовать на реакте?

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

Ну то есть


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

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

Не могли бы вы более подробно рассказать про простоту реюза компонентов c акцентом на то, что по вашему мнению здесь даёт реакт? Потому что в соответствии с моим опытом реюз stateless компонент — это такая боль, что как правило проще всё переписать заново

Компоненты не должны быть обязательно stateless. Внутренее состояние надо использовать, но использовать с умом. Всё, что касается внутренней логики компонента, должно происходить внутри него. Из хранилищ должна приходить общая картина приложения, состояния других компонентов, информация от сервера и т.д. Некоторые внутренние вещи даже не обязательно в state хранить. Локальные переменные класса никто не запрещает. State нужен только для той внутренней логики, которая сама вызывает перерисовку компонента. Компоненты совсем без состояния в большинстве случаев можно заменить на «тупые» функциональные компоненты.

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

И не стоит забывать, что часть компонентов отвечает за визуализацию, а часть за логику (и вперемешку тоже). Визуальные компоненты чаще всего будут stateless и очень просто интегрируются в нужное место. Они же чаще являются функциональными компонентами, чья задача — «пережевать» пропы и выдать результат.

Ничто не мешает использовать реакт, как, скажем, jquery-компоненты, или директивы ангуляра, или еще что-то другое. Но только зачем? App state management библиотеки придумали не потому, что в реакте по-другому нельзя, а потому, что это удобно и имеет преимущества. Об этом говорит и существование байндингов того же redux под многие фреймворки.

Какие преимущества и преимущества перед чем?

Не притворяйтесь, что не знаете

В мире есть десятки годных вариантов, позволяющих разделить логику проекта на основе React.js на более логичные структуры данных: flux, reflux, redux, mobx — это только то что я сам знаю.


Статья сильно напоминает попытку ввести наивных людей в заблуждение насчёт React.js.

Вот после сравнения кол-ва строк кода хочется просто не читать дальше.
А вы сами смотрели что сравнивают?
По моему когда сравнивают:
вот так
React
image

scala
image
о чем может быть еще разговор?

Даже если во втором случае добавить переносов перед каждым атрибутом, то всё-равно число строк меньше получится. Безотносительно данного примера, объём кода, необходимого для реализации одного и того же — не маловажная характеристика.


К слову сказать, формат view.tree просто не даст вам наговнокодить в одну строку, не навесить уникальный класс, и потом не потребуется рефакторинг всего кода для внедрения локализации:


$my_todomvc_head $mol_list sub /
    <= Title $mol_view
        sub / <= title @ \todos
    <= Add $mol_string
        hint <= add_hint @ \What needs to be done?
        focus <= add_focus true
        value?val <=> task_title_new?val \
        event_done?event <=> event_add?event null
Даже если во втором случае добавить переносов перед каждым атрибутом, то всё-равно число строк меньше получится.

Нет, не получится, оставшиеся строки в scala-варианте записаны опять же в одну:
<section class="todoapp">{ header.bind }{ mainSection.bind }{ footer.bind }</section>

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

Я согласен, что сравнивать строки можно лишь при эквивалентном форматировании. А лучше сравнивать объём, но при эквивалентном именовании. А совсем идеально — число лексем, но это уже весьма нетривиально.

Вы больны? Разницы в версии Scala и React тупо нет, кроме return
теперь из-за return менять библиотеку (библиотеку, карл), на другой язык с кривым портом этой же библиотеке? Что за новый вид мазохизма. Почему этот пост вообще оказался на главной? В комментариях треш — сравнение с Angular, jQuery. Реклама упоротого $mol (какой псих такое вообще в прод потащит?)

Повторю для особо здоровых: моё возражение никак не касалось ни реакта, ни скалы.


К $mol у вас какие претензии? Я был бы рад послушать конструктивную критику.

Считаю претензии к React необоснованными! В топку.

Какая-то реклама собственного фреймворка, на основе унижения React-а.
Участвую в проекте разработки CRM, пишем на React-е, есть огромная библиотека простых виджетов/ui-компонентов, которые используются/переиспользуются вполне благополучно. Да, тем кто раньше писал только ООП сложно поменять парадигму и перейти на функциональных подход к компонентам, поэтому все еще используют наследование и перегрузку методов, вместо HOC, но это лечится)

Согласен. Типичная тенденция по продвижению своего фреймворка. Пока не придумаешь, чем твое детище лучше React'a никто на него не посмотрит. При этом в React'e разбираться не обязательно =)
У вас есть верстальщики? Как они относятся к JSX?
Уточню сразу, не сарказм и не наезд, вполне искренний интерес.
Я не только лишь верстальщик, но думаю что верстальщики только от одного хотрелоада должны кипятком писать, потому что это очень удобно, особенно если код был написан так что не позволяет запускать какие либо компоненты отдельно от основного приложения, а тебе надо верстать что то что требует приводить приложение в определенное состояние.
Для таких случаев есть storybook — вот это действительно удобно.

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

функциональных подход к компонентам

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

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

Такой легкий компонент очень удобен при создании простых веб-страниц. Однако, когда требуется взаимодействие между несколькими компонентами, неизбежна передача функций обратного вызова (callback functions) в качестве параметра. В частности, для веб-страниц со сложными структурами приходится использовать десятки взаимосвязанных компонентов, в которых коллбэки передаются от родителей к потомкам из слоя в слой. Единственным результатом применения фреймворка React в таких сложных интерактивных веб-проектах будет то, что код станет слишком беспорядочным и трудноподдерживаемым.

Flux? Не, не слышал.

К сожалению, поддержка HTML в React является неполной. Разработчик должен вручную заменить class на classname, а for на htmlFor. Кроме того, синтаксис встроенных стилей необходимо поменять с CSS на JSON.

styled-jsx? Не, не слышал.

Минимальным блоком для повторного использования в React является компонент (React.Component). Он более лёгкий, чем Controller и View в AngularJS

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

Я вот не фанат реакта, но после оценки всего, что сейчас есть, выбрал именно его для создания сложного интерактивного приложения (причем это ГИС).
а во втором он идет как ключевой элемент работы
Угу, только никак не расширяется, так как нельзя отрендерить другой компонент в качестве хоста, добавив пропсов. «Добавить» пропсы вообще нельзя, так как нет спредов. Или нельзя отнаследоваться, так как метаданные декоратора не наследуются. Или нельзя создать HOC.

Так что «компоненты» там так себе, одно название.

Почему не выбрали VueJs?

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

JSX поддерживается, если нужно.


качество кода и его документирования

Может быть, но вот допустим документация отличная. И вообще сам фреймворк очень developers friendly, в консоли подсказывает на упущения и ошибки хорошо отображает.

приложение на фреймворке Binding.scala содержит всего 154 строки кода по сравнению с 488 строками на React

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

Решение: PureComponent'ы и Immutable структуры данных (можно просто следовать соглашению, можно использовать Immutable.js или другие решения)
Сравнение двух версий DOM медленное и подвержено ошибкам. Например, если вы хотите вставить элемент li в начало ul

Решение: параметр key. React сыплет в консоль warning'и если забыть добавтиь key там, где он нужен.
Таким образом, нельзя сказать, что React превосходит Cycle.js, Widok или ScalaTags.

Но автор сам абзацем выше говорит обратное: «React больше подходит для повторного использования HTML-шаблонов по сравнению с другими фреймворками»
Даже используя propType React сможет найти ошибки только во время работы программы

Решение: Flow
Мы можем использовать Binding.scala для решения сложных проблем, которые React решить не может.

А теперь найдите мне для моего проекта пяток разработчиков на Scala и пяток на React. Про разнообразие сторонних компонентов для обеих платформ вообще молчу.
Правда в основном за счет комментов и того что в JSX принято переносить каждый атрибут на новую строку

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


val completed = TodoList("Completed", "#/completed", for (todo <- allTodos if todo.completed) yield todo)

  • за такое надо бить

Решение: PureComponent'ы и Immutable структуры данных (можно просто следовать соглашению, можно использовать Immutable.js или другие решения)

это здесь вообще не при чём. Пойнт в том, что Реакт тупо делает лишние вычисления, рендрит свой VDOM на каждый чих по новой. В отличие от действительно реактивных систем (Elm). Наличие или отсутсвие персистентных структур данных к этому ни какого отношения не имеет. Кроме того, эта самая Immutable.js кривая и безбожно тормозит


Решение: Flow

решение: Typescript. Больше типизации, меньше инструментов, меньше кода, меньше мартышечьего труда

Рендер VDOM дешевый. И его можно обойти через sCU

простой пример: у вас есть список в несколько тысяч записей (а у некоторых пользователей — десятков тысяч), по этому списку вы строите дерево (+50мс), потом вы это дерево фильтруете по пользовательскому фильтру (+150мс), потом для каждой записи создаёте пункт меню (+50мс). Итого — четверть секунды на первичный рендер. По пункту меню можно кликнуть и он становится текущим. Что происходит при переключении между записями?


  1. Подход реакта. Заново формируем то же самое дерево (+50мс), заново фильтруем его с тем же результатом (+150мс), заново преобразуем в список пунктов меню (+50мс). Если мы не поленились реализовать sCU или делать всё на иммутаблах (а обычно об этом вспоминают, когда пользователи начинают жаловаться, что у них всё тормозит), то хотя бы не будет генерироваться virtual-dom для всех пунктов меню, а только для изменившихся (-40мс). После чего реакт делает дифф с real-dom и применяет разницу (+40мс). Итого — четверть секунды на удаление атрибута у одного элемента и добавление его другому.


  2. Эффективный подход. Удаляем атрибут у одного элемента (+1мс), добавляем его другому (+1мс). Даже если мы накинем сюда 50мс на обеспечение реактивности, то это всё равно будет в 5 раз эффективней подхода с виртуальным домом.

Числа взяты с потолка для иллюстрации того, что "рендер VDOM дешёвый" только лишь в простейших случаях, а sCU — не серебрянная пуля, решающая все проблемы с производительностью. Тут нужно промежуточное кеширование и своевременная инвалидация кеша на уровне данных, а не только на уровне рендеринга. А если у вас уже есть механизм обеспечивающий реактивную архитектуру, то виртуальный дом тут нужен как собаке пятая нога.

у вас есть список в несколько тысяч записей (а у некоторых пользователей — десятков тысяч)
Ума не приложу, кому может понадобиться единовременно рендерить такой список. Это ж какой должен быть монитор, чтобы это все влезло. Рендерить можно только видимую часть и буферную область в обе стороны списка, чтобы не тратить время и не жрать память.

Тут речь про исходные данные. Исходных данных может быть и куда больше. Рендерить вы можете хоть вообще только один пункт меню, что не избавляет вас от необходимости делать полную обработку данных от начала и до конца, чтобы понять какой именно пункт меню надо отрендерить.

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

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

Я почитал комментарии и заметил, что с вами вести адекватную беседу сложно, так как вы прыгаете с темы на тему. У нас есть проблема, большой список. Ниже вам описали, как решить ее. Вы тут же переходите на обработку данных.
Я бы с радостью обсудил проблемы React, но, увы, мало кто способен на это. Хотелось бы разобрать все по полочкам

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

1. Подход реакта

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

Только ведь не спроста в итоге многие изначально-реактивные решения позже начинают использовать vdom. Так было и с ember и с vue, например.
Тут столько всего не соответствует действительности, что даже начинать не хочется. Тем более судя по другим комментариям это все-равно бессмысленно.

Если видите, что я где-то не прав — поправьте. Даже если я с вами не соглашусь, из-за своего недалёкого ума, это будет полезно узнать читателям.


Только ведь не спроста в итоге многие изначально-реактивные решения позже начинают использовать vdom. Так было и с ember и с vue, например.

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


Так было и с ember и с vue, например.

А с Angular и $mol_view так не было, например. Они применяют точечные патчи. Более того, можно использовать JSX с прямым изменением реального дома, получая более производительное решение.

Подход реакта.
Заново формируем то же самое дерево (+50мс),
заново фильтруем его с тем же результатом (+150мс),
заново преобразуем в список пунктов меню (+50мс).… После чего реакт делает дифф с real-dom и применяет разницу (+40мс). Итого — четверть секунды на удаление атрибута у одного элемента и добавление его другому.

Итого — 90мс для списка в 10 000 элементов.


Перестроение — это вообще не про реакт, согласны? Это, возможно, про редакс или флакс, но речь разве о них?


Вы ниже писали, что Ангуляр применяет точечные патчи.
Вы думаете, что это быстрее генерации и сравнения v-dom для списка в 10К элементов? Возможно, но я бы не утверждал. Сравнение произвольных данных может быть как быстрее, так и медленнее в-дом, зависит от данных.


В "реактивных" фреймворках типа MobX или Knockout время тратится на построение графа зависимостей. Будет ли это быстрее, чем в-дом? Может да, а может и нет, зависит будут ли меняться данные, как часто и каким образом они будут меняться.


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


Выбирать фреймворк по производительности, если производительность отличается, ну, пусть даже на 50% — нету смысла, если только заранее не известны узкие места, и они действительно критичные и важные (придуманный пример — обновление котировок, или лотов аукциона, тяжелые графики на SVG, и т.п.).


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

обновление котировок
Рисуем по несколько виджетов на странице с тикающими таблицами котировок, используя при этом redux. Каких-то видимых проблем с производительностью не испытываем. Всегда ведь можно подкрутить, независимо от технологии.
Перестроение — это вообще не про реакт, согласны? Это, возможно, про редакс или флакс, но речь разве о них?

Речь про подход реакта. И я тут считал задержку между действием пользователя и появлением реакции на экране, а не только лишь время процессора, проведённое в определёной библиотеке.


Вот, например, описание этой проблемы и костыля к редуксу, для её решения: https://github.com/reactjs/reselect#motivation-for-memoized-selectors


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


Сравнение произвольных данных может быть как быстрее, так и медленнее в-дом, зависит от данных.

На каких же данных вдом может быть быстрее? Уже для 3000 элементов, разница заметна: http://mol.js.org/app/bench/#sample=react-15-3-2~tsx~angular-1-5-5/sort=update


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


В "реактивных" фреймворках типа MobX или Knockout время тратится на построение графа зависимостей.

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


и вы для демонстрации выбрали худший для в-дом, вот где непредвзятость

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


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

Вы очень смело обобщаете. с какими фреимворками вы работали? работали ли с $mol? сможете придумать для него худший релистичный сценарий?


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

согласен, но при чём тут выбор фреимворка? Мы говорили про вдом, у которого из преимуществ есть только мифическая скорость.


80% приложений никогда не испытают проблем с производительностью, а половина тех, что испытают — решат их минимальными исправлениями.

На мощной рабочей станции не испытывают. Запустите на мобиле и увидите тормоза. А архитектурные проблемы минимальными исправлениями не решаются в принципе.

Перестроение — это вообще не про реакт, согласны? Это, возможно, про редакс или флакс, но речь разве о них?

Речь про подход реакта. И я тут считал задержку между действием пользователя и появлением реакции на экране, а не только лишь время процессора, проведённое в определёной библиотеке.

Вот, например, описание этой проблемы и костыля к редуксу, для её решения: https://github.com/reactjs/reselect#motivation-for-memoized-selectors

Я же и говорю — это костыли для редакса, не для реакта. Редакс бывает и для Ангуляра, и для Вью, и даже для Нокаута.


На каких же данных вдом может быть быстрее? Уже для 3000 элементов, разница заметна:

1) На больших объектах, со множеством свойств — когда у элемента виртуального ДОМ свойств меньше. 2) На больших массивах (да и объектах), из которых рендерится только часть (то же отфильтрованное меню) — сравнение будет идти по реально отображаемым элементам, а не по всем данным.


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


Вы очень смело обобщаете. с какими фреимворками вы работали? работали ли с $mol? сможете придумать для него худший релистичный сценарий?

Я не работал с ним, как я понимаю, это фреймворк с построением графа зависимостей. Плохой сценарий для него — это 1) перестроение виджета по новым данным (при перезагрузке с сервера, например), 2) сложные проекции (селекторы) — производные вычисляемые данные, которые не сделать наблюдаемыми (т.к. они вычисляются каждый раз заново), и, следовательно, ДОМ для них просто тупо перестраивается.

Я же и говорю — это костыли для редакса, не для реакта. Редакс бывает и для Ангуляра, и для Вью, и даже для Нокаута.

Только в реакте без него совсем больно, а в нокауте оно как пятое колесо :-)


сравнение будет идти по реально отображаемым элементам, а не по всем данным.

Разумеется делать dirty-checking по редуцированным данным быстрее, чем по исходным. Однако не делать dirty-checking — ещё быстрее.


перестроение виджета по новым данным (при перезагрузке с сервера, например)

Все объекты (не только визуальные компоненты) переиспользуются, так что это вполне штатный сценарий.


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

Например?

Все объекты (не только визуальные компоненты) переиспользуются, так что это вполне штатный сценарий.

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


Это тот же dirty checking, только вручную. А если все пересоздается заново — то тот же рендеринг v-dom.


Тут природу не обманешь, в любом из подходов есть неэффективные сценарии.


Например?

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


Рассмотрим каждый подход и вариант с пересозданием или же модификацией:


  • С dirty-checking — разницы особо нет, при модификации будет чуть меньше проверок (или же вообще не распознает изменения). Реальный ДОМ обновится как карта ляжет — может оптимально, а может и пересоздать заново большой кусок.


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


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

При этом модификация вручную — это императивный (не "реактивный") код, и он по производительности будет сопоставим с dirty checking и v-dom reconciliation. Пересоздание графа зависимостей же — это самая затратная операция из всех, и если ее делать постоянно, то выигрыша в производительности не будет.


Виртуальный ДОМ — это не серебряная пуля, конечно же, это всего лишь разумный компромисс, который дает аккуратное и предсказуемое (а это не менее важно часто, чем быстрое) обновление реального ДОМ дерева.

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

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

Да, для эффективности больших обновлений нужно делать "транзакционные" методы

Ну, наверное, из-за возможности «транзакционных» апдейтов нескольких кусков стейта?
То есть обновляются значения всех свойств во всей иерархии? Или обновляются только измененные?

Обновляются зависимые от изменённых.


А массивы как, как определяется, какие элементы в массиве изменились? Или если порядок поменялся, как определяется?

Поверхностным сравнением.


Это тот же dirty checking, только вручную.

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


Например, из массива чисел подготовить данные для графика;

Как раз недавно этим занимался. У меня получилась такая последовательность:


  1. Каждый график принимает список чисел и по нему генерирует список точек.
  2. По списку точек вычисляются габариты.
  3. Диаграмма пробегается по всем графикам, запрашивает их габариты и формирует общий габарит.
  4. По визуальным и дата-габаритам вычисляется масштаб и смещение.
  5. Каждый график берёт масштаб и смещение и по ним вычисляет визуальные координаты точек.
  6. Из него формируется список координат, где каждая точка отстоит от другой минимум на N пикселей.
  7. Из координат формируется path строка, которая вставляется в соответствующий атрибут.

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


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

С чего бы полное перестроение? У реакта реконциляция работает лишь на одном уровне, да и то, если имя тега не поменялось. А если вдруг поменялось — всё поддерево будет уничтожено и создано вновь. В случае $mol_view каждый компонент имеет ссылку на соответствующий ему элемент. И куда бы вы его не переместили — элемент будет туда перемещён вместе со всем своим поддеревом. Например, вы можете перетащить виджет из одного блока в другой и на это и уйдёт ровно 2 операции с домом — удаление в одном месте и добавление в другой. А в некоторых случаях хватит и только второй операции.


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

Построение графа, конечно, не бесплатно, но и не так уж затратно, а профита даёт много:


  1. Независимость от размеров приложения — обновления идут всегда по короткому предсказуемому пути.
  2. Мы всегда знаем какие объекты нам нужны, а какие можно безболезненно удалить из памяти.
  3. Мы всегда можем посмотреть что от чего зависит.

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

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

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

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


С чего бы полное перестроение?… В случае $mol_view каждый компонент имеет ссылку на соответствующий ему элемент. ...

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


Построение графа, конечно, не бесплатно, но и не так уж затратно, а профита даёт много:. ...

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


То есть модель с графом зависимостей тяготеет к императивному стилю.


Затесалась где-то лишняя обёртка или другое имя тега и привет, полный ререндер.

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


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

Понятно, что вручную обычно быстрее, т.к. учитывает специфику данных.

Вы видимо не заметили предлог "не" :-) "И нет, вручную это проверять не надо."


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

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


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

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


То есть модель с графом зависимостей тяготеет к императивному стилю.

К декларативному, математичному, но не функциональному. Простой пример:


class $my_app {

    @ $mol_mem()
    users( next? : $my_user[] ) {
        return next || [ this.user( 0 ) , this.user( 2 ) ]
    }

    @ $mol_mem()
    filter( next? : string ) {
        return next || ''
    }

    @ $mol_mem()
    users_filtered() {
        const filter = this.filter()
        return this.users().filter( user => user.name().match( filter ) )
    }

}

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


Да, это так (наверное)

https://facebook.github.io/react/docs/reconciliation.html


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

Да там не нужна какая-то особая оптимизация, нужно просто сделать реконциляцию по уму. Например, вот так: https://github.com/eigenmethod/mol/blob/master/dom/make/make.ts#L12


Если их структура похожа, то будет происходить обновление физ. ДОМ вместо пересоздания.

Для этого не нужен виртуальный дом. Например, тут создаётся сразу реальный дом с переиспользованием существующих нод: https://github.com/eigenmethod/mol/tree/master/dom/jsx


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

Гораздо удобней работать с нормализованными данными, а не с развесистым JSON.

Гораздо удобнее работать с полноценными объектами :)

Вот вы приводите пример с мемоизирующими затычками $mol_mem. Как это согласуется с вашим же утверждением выше о костыльности мемоизирующих селекторов reselect?

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

Реселект придумали не для того, что обновление дом не тормозило, а для того, чтобы при изменении данных не выполнять по этим данным дорогие фильтры, например выборки из массивов. Это ровно то же, чем занимается ваш $mol_mem. Только в случае с реселектом мне не нужно загрязнять декораторами изначальную структуру данных.

Проверки на изменение входных параметров контейнера завернуты в redux connect — та же мемоизация. Проверки входных параметров компонента можно проводить в scu — та же мемоизация. То, что вам почему-то это кажется костылем, вызывает лишь недоумение.

@raveclassic, потому что это следствие того, что redux архитектура заставляет пересчитывать одно и тоже ввиду того, что в иммутабельных структурах при изменении чего бы то ни было снизу, необходимо менять и все вышестоящие обёртки. А т.к. никакого графа зависимостей нет, то и, в случае react, зависимые react-компоненты вынуждены на всякий случай проводить rerender virtual-dom-а. а т.к. для redux жизненно необходима нормализация данных, то и разные сложные вычисления выпадают сюда же. И без мемоизации оно всё приедет не успевши завестись. Да вы и сами всё это знаете. По сути pureComponent в React это та же мемоизация.


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

faiwer Скорей всего, я чего-то недопонимаю, но чем вам композиция селекторов не «граф зависимостей»?

Переписал примерчик выше на селекторах:
type $my_user = {
    id: number,
    name: string
};

type $my_app = {
    users: {
        ids: number[],
        entities: {
            [id: number]: $my_user
        }
    }
};

const app = (app: $my_app) => app;
const arg = <T>() => (app: $my_app, arg: T): T => arg;

const user = createSelector(
    arg<number>(),
    app,
    (id, app) => app.users.entities[id]
);

const users = createSelector(
    arg<$my_user[] | undefined>(),
    app,
    (users, app) => users || [
        user(app, 0),
        user(app, 1)
    ]
);

const filter = createSelector(
    arg<string | undefined>(),
    arg => arg || ''
);

const users_filtered = createSelector(
    users,
    filter,
    (users, filter) => users.filter(
        user => user.name.match(filter)
    )
);

const initial: $my_app = {
    users: {
        ids: [0, 1],
        entities: {
            [0]: {
                id: 0,
                name: 'User1'
            },
            [1]: {
                id: 1,
                name: 'User2'
            }
        }
    }
};

const result = users_filtered(initial);


Первый же экшен запустит выполнение селекторов в контейнерах, и если селектор в connect возвращает новое значение, то контейнер начнет перерисовываться.

PS. arg выглядит костыльно из-за того, что текущий TS 2.2 не поддерживает дефолтные типы в дженериках, а именно в Selector<TInput, TOutput> можно было бы добавить третий аргумент для props: Selector<TInput, TOutput, TProps = any> Но это уже относится к TS, а не к реселекту. Для обычного JS можно просто взять (state, arg) => arg
faiwer Прошу прощения за обман. Селектор app в user нужно, конечно же, заменить на app => app.user.entities, чтобы user срабатывал только на изменение entities. Соответственно, user будет выглядеть вот так:
const user = createSelector(
    arg<number>(),
    app => app.users.entities,
    (id, users) => users[id]
);

Мне в redux не нравится то, что store это такая монолитная штукая, на которую всё завязано, и соответственно, при любом малейшем изменении в этом store, из-за immutable-природы нужно его "перевязывать" (пересоздавать все обёртки). А т.к. нет никаких, заранее (например декларативно) прописанных связей вида "что кому от кого интересно", то система вынуждена при любом изменении в store (он же падла монолитен) проверять все свои connection-ы на предмет, а не изменилось ли чего. Причём вообще все. На любой чих. Затем эта болезнь переползает на react-компоненты, заставляя часть из них пересоздавать virtualDOM, а затем и сверять его с предыдущим. И если изменение было большим, то вся эта возня потеряется на фоне реальных вычислений. А если изменение было крошечным, то КПД такой вот трудовой деятельности будет ну просто ниже плинтуса.


Вообще вся такая архитектура заставляет сильно нормализовывать данные, мемоизировать даже .filter-ы, и прилично вывернуть мозг наизнанку. Ну совсем другой подход. В императивных реактивных фреймворках но всё куда прямолинейнее происходит, что-ли. Куда меньше всей этой мышиной возни, но в результате куда менее предсказуемее всё работает. Отладка сложных случаев в нокауте подобно аду. Местами уже прямо костылями покрываешь код, т.к. разобраться в этом смертному становится малореально. В то время как в react-redux достаточно просто слепка store-а и не отходить от правил игры.

Selector-ы, конечно же, тоже имеют граф зависимостей, т.к. не пересчитывают своё тело, пока не изменятся их "зависимости". Но оно при каждом изменении store-а (если мы про redux) вынуждено проверяет изменённость всех полей. В случае knockout-а этой лишней работы произведено не будет. Более того, в случае мутабельности, при мутациях, не будут затронута вся вышестоящая иерархия от объекта и по сути никаких лишних ни сравнений, ни вычислений произведено не будет. Ну вот прямо от слова совсем. Точечные изменения сразу по месту. И речь я щас не про DOM. Но вся эта магия обеспечивается весьма тяжёлой обвязкой каждой такой вот отслеживаемой переменной, в то время как react и redux позволяют (и требуют) обходиться plain-object-ми. Палка о двух концах.

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

Вот это и есть ключевая проблема. Обновить модель по новым данным просто, если она тривиальная. Если нет — то сложно, и будут те же костыли, что и в реакте/ангуляре, только вручную.


Ну а "нетривиальных структур данных" в модели быть не должно. Гораздо удобней работать с нормализованными данными, а не с развесистым JSON.

Много чего не должно быть, однако есть. Те же лишние дивы-врапперы, появляющиеся по условию, которые вы упоминали выше. Спорить не с чем, просто уточняйте, что $mol — самый лучший фреймворк (для простых и нормализованных моделей). Замечу, однако, что в dirty checking и virtual DOM такой проблемы нет.


...
@ $mol_mem()
users_filtered() {
const filter = this.filter()
return this.users().filter( user => user.name().match( filter ) )
}
...

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


Я же имел ввиду нечто вроде


...
user_stats() {
   return this.users().reduce((result, user) => {
           return {
                 total: (result.total || 0) + 1,
                 total_age: (result.total_age || 0) + user.age 
           };
    }, {});
}
...

Пример притянут, конечно, за уши. Но суть в том, что каждый вызов создаст новый объект, и магия графа зависимостей перестанет работать и пересоздаст кусок разметки.


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


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

В Реакте, если я не ошибаюсь, она контролируется через key. Это не совсем то, что у вас, но позволяет указать, когда элемент должен переиспользовать реальный ДОМ элемент (но опять же, я не уверен).

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

Может приведёте пример сложной модели? А то похоже мы вкладываем в это понятие разные свойства.


Те же лишние дивы-врапперы, появляющиеся по условию, которые вы упоминали выше. Замечу, однако, что в dirty checking и virtual DOM такой проблемы нет.

Как мы выяснили выше, реконциляция у реакта в этом случае ломается, а у "самого лучшего фреймворка" — нет. Если же вы тут про "сложные модели", у них полно других проблем, никак не связанных с рендерингом. Например, дублирование информации вплоть до O(n^n), избыточный трафик с сервером и тп.


Пара примеров из жизни:


  1. В одном проекте было иерархическое меню и приходило он в виде развесистого JSON. Сервер парился со сборкой этого дерева, клиент парился с его разборкой. В какой-то момент решили позволить одному ребёнку иметь несколько родителей и понеслось: сервер отваливается по таймауту пытаясь собрать из 1000 записей гигантское дерево, трафик вырос на порядок ибо дерево стало весить десятки мегабайт, а если дело всё же доходило до клиента, то он пережёвывал это дерево не одну секунду. Решение простое и очевидное, для более-менее опытного архитектора, — выдавать данные в нормализованном виде (коллекции записей с перекрёстными ссылками). И (внезапно) код сервера упростился до "выплюнуть кусок таблицы из базы данных", трафик упал до исходных значений, код клиента избавился от рекурсий.


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


  3. В третьем проекте сразу делали нормализованно. Модель запрашивала лишь те поля, что ей нужны в данный момент. Сервер возвращал типизированные коллекции записей с перекрёстными связями. Простым универсальным адаптером, ответ сервера переводился в модели. Модель могла работать как на клиенте (через http и websocket адаптеры), так и на сервере (через database адаптер). Изменения в бизнес требованиях не приводили к деградациям и вообще по минимуму затрагивали модель.

Я же имел ввиду нечто вроде

Оно реализуется более естественным способом:


@ $mol_mem()
users_total() {
   return this.users().length
}

@ $mol_mem()
users_total_age() {
   return this.users().reduce( ( total_age , user ) => total_age + user.age() , 0 )
}

@ $mol_mem()
user_stats() {
    return {
        total : this.users_total() ,
        total_age : this.users_total_age() ,
    } 
}

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

Но даже в вашем примере не будет пересоздания куска разметки, так как реконциляция в $mol не зависит от данных, а зависит от идентификатора, который использует разработчик для получения объектов. Например, создание компонент (rows) для списка задач (tasks):


// Возвращает список компонент для отображения
rows() {
    return [ this.Head() , ... this.tasks().map( task => this.Task_row( id ) ) ]
}

// Фабрика, создаёт и контролирует время жизни шапки.
// Идентификатор будет вида: $my_app.Root(0).Head()
@ $mol_mem()
Head() {
    return new $my_head
}

// Фабрика, по ключу создаёт и контролирует время жизни строки одной задачи.
// Идентификатор будет вида $my_app.Root(0).Task_row("123") даже если мы будем вкладывать строки друг в друга
@ $mol_mem_key()
Task_row( id : string ) {
    const row = new $my_task_row
    row.task = ()=> this.task( id )
    return row
}

В Реакте, если я не ошибаюсь, она контролируется через key. Это не совсем то, что у вас, но позволяет указать, когда элемент должен переиспользовать реальный ДОМ элемент (но опять же, я не уверен).

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

id забыл получить, правильно так:


rows() {
    return [ this.Head() , ... this.tasks().map( task => this.Task_row( task.id() ) ) ]
}
Если же вы тут про "сложные модели", у них полно других проблем, никак не связанных с рендерингом. Например, дублирование информации вплоть до O(n^n), избыточный трафик с сервером и тп.

А вы не путаете сложные модели с их представлением на транспортном уровне? Передавать от сервера к клиенту часто удобно в нормализованном виде, но вот реализовать хоть на сервере, хоть на клиенте логику в виде orderManagerName = managersById[ordersByIds[orderId].managerId].name не так удобно, как orederManagerName = orders.get(orderId).manager.name, при том, что объект каждого менеджера существует в единственном экземпляре, а дублируется он на транспортном уровне между сервером и клиентом (или между сервером и СУБД) — дело десятое.

О том и речь, что модель — это не тупо JSON, а API для работы с данными и под капотом этому апи удобнее работать с номализованными данными.


orderManagerName = domain.order( orderId ).manager().name()

manager() и name() в вашем примере — это тупые геттеры или что-то вроде


 return  domain.manager(this.managerId);

?

Это, я так понимаю, издержки фреймворков с графом зависимостей (хотя не знаю, почему не использовать свойства вместо этого).

Скорее конкретного фреймворка.

Я позволю себе "вклиниться" по поводу сравнения фреймворков и $mol. Почитал про него Ваши статьи — выглядит многообещающе благодаря tree, но это… революция? Как истинный конформист подожду success stories )) Пока что из всех решений для безобразий в браузере мне предпочтительней Elm. Он кажется игрушечным, но на деле перформанс в 2 раза выше в сравнении с реактом, не надо мучатся с вебпаками-бабелями, персистентные структуры данных из коробки и куча ништяков ещё.

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

Использовать объект и хранить список айдишников отдельно? Такой паттерн в документации Redux описан. React тут вообще не причем.

Вам для фильтрации всё-равно нужны будут объекты :-)

Компоненты React трудно использовать повторно в сложных веб-проектах.

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

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

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

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

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

Правда для 1-го aпреля.

Фреймворк, HTML-шаблоны
Явно автор знает, о чем поветсвует

Сравните лучше с VueJS, то что риакт не нужен люди и так уже начинают понимать.

Автору читать документацию + изучить redux.

И сложных проектов на реакте уже полным- полно: тинькоф, сбербанк, почта России и т.д

То ли автор не осилил реакт, то ли просто пытается пиарить scala фреймворк, не пойму

Я являюсь большим любителем как scala, так и scala.js. Однако считаю, что данная статья — это худшее, что можно было выбрать для перевода. Я думаю, что автор статьи просто попытался как-то привлечь внимание к собственной разработке и выбрал темный путь: засрать популярную в js мире библиотеку, чтобы у огромной аудитории бомбануло и она пошла в комменты. И как побочный эффект — эта аудитория всё-таки узнает о Binding.scala.
Касательно самой статьи уже всё в общем-то написали. Повсеместное манипулирование фактами, умалчивание очевидных вещей и указания на сильные стороны Binding.scala.

Господи сделайте меня развидеть это. (Квази)ФП библиотека у него плохо для реюзабельности подходит. Жёсткая изоляция классов ему не добавляет композиционных возможностей. Проблема № 4 про общение с сервером добила с концами. Ничего что именно реактивная природа реакта позволяет изолировать представление от работы с сервером? Алсо видимо это вина реакта в том что кто-то не умеет в асинхронный код, или том, что даже Fetch API не добавляет изящества работе JS с сервером.
Эти люди, кажется, вообще не понимают зачем сделан реакт. Реакт сделан что бы писать меньше if'ов когда выбираешь какой шаблон отрендерить следующим. Всё остальное — боль везде и зависит от таланта программиста.
Всё остальное — боль везде

Не везде.

Sign up to leave a comment.