Комментарии 169
Virtual DOM в React является медленным и неточным
В движке Fiber проблема производительности уже неактуальна
React требует сложного асинхронного программирования при общении с сервером
Напротив, в связке с Redux весь код бизнес-логики на клиенте может быть сведен в единое место, представляемое одним или несколькими комбинированными reducer-ами. Продолжительные по времени действия легко описать в sagas, а еще лучше реализовать всю архитектуру через events — в таком случае явные асинхронные вызовы вообще не нужны
Даже используя propType React сможет найти ошибки только во время работы программы, а не во время компиляции.
Если нужна статическая проверка, можно использовать Flow
Поддерживает ли Binding.scala серверный rendering?
Если нужна статическая проверка, можно использовать FlowА лучше вообще на TypeScript перейти
Продолжительные по времени действия… еще лучше реализовать всю архитектуру через events — в таком случае явные асинхронные вызовы вообще не нужны
Куда можно сходить посмотреть примеры?
Если нужна статическая проверка, можно использовать Flow
Костыли в виде Flow не нужны (уже не нужны), существует более полноценное решение в виде TypeScript. Если TypeScript не дружит с React, то это проблема React и сообщества его фанбоев.
jQuery наше все :)
Ничего, вебассембли доделают до нормального состояния, получим десктопные фреймворки прямо в браузере. Ибо они даже при рендеринге на <canvas> через вебсокет работают вполне пристойно.
Поэтому та разница в подходе к разработке веб и десктопного интерфейса не имеет под собой каких-то принципиальных архитектурных обоснований. Только потому что так исторически сложилось. Потому что десктопные технологии разработки приложений изначально росли, как… технологии разработки приложений. А веб-приложения выросли из нехитрого языка разметки, который предназначался для отображения картинок и гипертекста в браузере, и простенького скриптового языка для оживления статических элементов.
И как же интересно вы делаете адаптивность на десктопе?
По-всякому бывает. Иногда тянули на всю ширину, иногда скрывали и перестраивали фреймы. А что особенного в адаптивной вёрстке в вебе нынче? «Полностью перекомпоновать интерфейс» в 95% случаев эквивалентно «выстроить блоки не в ширину, а лентой, а меню убрать под кнопку». Причём на уровне модных библиотек CSS уже всё настроено, только не поленись нужные стили прописать.
Оффлайн режим
Разве это какая-то особенность, которая недоступна десктопным приложениям? Или вы опять путаете историческую костыльность решения этой типовой задачи для веб-приложений с какими-то архитектурными потребностями?
SEO
Да, только SEO лежит далеко-далеко за рамками архитектурных вопросов разработки веб-приложений. От разработчика тут требуется лишь отсутствие ошибок вёрстки, а остальное уже в компетенции контент-менеджера и сеошника.
непредсказуемые платформы
Странно, как у вас поворачивается язык называть «непредсказуемыми платформами» три браузерных движка, которые существуют в нашей эпохе, и которые имеют лишь небольшие отличия в поведении. Вы вот вспомнили бы, сколько головной боли в своё время доставило появление UAC или выноса локальных данных приложений в пользовательский профиль на винде :)
фронтенд за последние несколько лет ушел далеко вперед
Куда именно он ушел? :)
отстали люди с jquery
В чем же?
Эти все JS-фреймворки в основном не нужны.
Они лишь привносят проблемы и сложности, которые разработчики потом героически решают.
Всё же не стоит равнять всех под одну гребёнку. После того как попробовал реактивное программирование. где приложение работает по твоим правилам, очень больно возвращаться на какой-нибудь 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, задачи были не те. :)
Может у нас разные задачи и Реакт не всем подходит? :)
Fixed.
Не то что я особый поклонник React и ненавистник Scala, но не является ли это очередным случаем "я считаюсь fullstack, но ниасилил React и вообще фронт не люблю"?
что приложение на фреймворке Binding.scala содержит всего 154 строки кода
А сколько весит scala.js (имеется ввиду скаловский runtime)? Года три назад было 20 мегов.
На сколько я понимаю, никто не заставляет тащить с собой весь рантайм. Тот же ToDoMVC весит 300кб, что даже меньше, чем реализация на реакте.
В варианте скалы тоже не всё минифицировано. Да и сравниваю я зазипованные объёмы. В любом случае далеко не несколько мегабайт. К слову сказать, вариант на неминифицированном $mol весит вообще 50кб.
Компоненты React трудно использовать повторно в сложных веб-проектах.
Разрабатываем и поддерживаем интерактивный фронтэнд для большого и сложного проекта. Успешно переиспользуем компоненты. Callback'и есть только в качестве обработчиков событий у дочерних компонент (прямо как в натуральном DOM). Всё очень хорошо выходит.
Получилось, правда, не с первой итерации, но всему же надо учиться. Повторное использование компонент повлияло как раз на переход к React, это вообще одно из его основных преимуществ. Вся логика, связанная с контекстом использования, контролируется через пропы, и это очень наглядно.
Веб-разработчики вынуждены использовать свойство key, а также методы shouldComponentUpdate и componentWillUpdate, чтобы помочь фреймворку угадать правильно.
Свойство key служит ключом при сортировке элементов, а хуки помогают корректно и только при необходимости обновлять компонент. Ведь пропы могут содержать довольно сложные структуры! Конечно, разработчик должен не бездумно подходить к созданию своего кода, находить места для оптимизации.
К сожалению, поддержка HTML в React является неполной. Разработчик должен вручную заменить class на classname, а for на htmlFor.
Автор, кажется, даже не открывал документацию и совсем не понял, что такое JSX и зачем оно нужно. Эх…
Не подменяйте понятия.
генерируя новый виртуальный DOM на каждый чих, как в публикации и пишется.А вы тоже не подменяйте.
Эта ветка не про скорость генерации vdom, а про буферизацию доступа.
Нативный JS не отменяет необходимости писать эффективный код.Все верно, но это совсем не про vdom.
Точно так же если тупо писать на Реакте «в лоб»
множественные разовые обновления буферизуются по фрейму
Это относительно редкий кейс. Чтобы эта буфферизация сработала, необходимо, чтобы событие всплывало чаще 60 раз в секунду. Кроме того, гораздо эффективней не буферизировать обновления ДОМ, а производить пересчёт данных не чаще 60 раз в секунду. Но для этого нужна совершенно другая (ленивая) архитектура, под которую реакт совершенно не приспособлен.
Повторное использование компонент повлияло как раз на переход к React, это вообще одно из его основных преимуществ
Переход с чего? Преимуществ перед чем? Что-то мне подсказывает, что вы могли бы произвести компонентную декомпозицию никуда не переходя.
Вы так и не ответили с чего переходили.
Приведёте пример такого независимого блока?
Всё зависит от Вашей специфики. Мы торгуем. У нас есть элементы, вроде корзины покупателя или формы оплаты, которые можно встраивать в разные контексты и получать единое настраиваемое представление. Я не знаю, что Вы хотите мне доказать, потому что я не своё мнение описываю, а с чем имею дело в работе. Я нигде не сравнивал React ни с чем, не говорил, что он лучшее решение из имеющихся в индустрии. Только лишь указал, какие его «минусы» по мнению автора вызвали у меня негодование. Часть этих минусов от непрочтения документации автором. А тот, к которому прицепились вы, вообще является преимуществом технологии. Не над чем-то преимуществом, а в целом — то, что записывают в графу «Плюсы».
В общем, если Вы хотите посравнивать и поспорить, я в это влезать не буду, уж простите.
Без примеров кода, ваши заявления довольно голословны. А без кода мне приходится гадать на кофейной гуще:
- ваш компонент корзины сам стучится за данными на сервер, тогда у периодически возникает несогласованность состояний разных частей приложения.
- ваш компонент корзины сам стучится в модель, тогда вы не можете создать, например, две корзины, и реализовать перекладывание товара из одной в другую.
- ваш компонент корзины не имеет состояния, тогда для её работы необходимо каждый раз реализовывать логику взаимодействия с состоянием.
Какой из этих вариантов по вашему можно считать "успешным"?
В общем, понятно — однонаправленное изменение данных, события и подписки, никто никуда не стучится.
То есть компоненты не являются самодостаточными. Чтобы где-то воспользоваться "готовой" компонентой необходимо обеспечить её соответствующим набором экшенов, сторов и редьюсеров. И речь не только о "доменной модели", но и о тех состояниях интерфейса, которыми нужно управлять из вне.
А какой еще возможен вариант? Почему он более "успешный"? И в чем проблема его реализовать на реакте?
Например, когда компонент самодостаточен и не требует какого-то особого окружения, когда с ним можно по разному взаимодействовать, а не только отрендерить, когда его можно детально настроить и динамически менять эти настройки (а настройки могут и не касаться рендеринга).
Не могли бы вы более подробно рассказать про простоту реюза компонентов c акцентом на то, что по вашему мнению здесь даёт реакт? Потому что в соответствии с моим опытом реюз stateless компонент — это такая боль, что как правило проще всё переписать заново
Нам React позволяет создавать такие блоки, на страницах, которые могут «постоять за себя», но при этом легко встраиваются в произвольную среду за счёт параметризации. И это поведение наслаивается. Компонент не обязательно является финальной точкой в дереве, он часто разбивается на более мелкие составляющие, которые в свою очередь могут быть применены в других аналогичных родительских компонентах. То есть до определённого момента мы параметризируем и переиспользуем родителя, а в сложных ситуациях можем создавать альтернативные блоки переиспользовать детей.
И не стоит забывать, что часть компонентов отвечает за визуализацию, а часть за логику (и вперемешку тоже). Визуальные компоненты чаще всего будут stateless и очень просто интегрируются в нужное место. Они же чаще являются функциональными компонентами, чья задача — «пережевать» пропы и выдать результат.
Ничто не мешает использовать реакт, как, скажем, jquery-компоненты, или директивы ангуляра, или еще что-то другое. Но только зачем? App state management библиотеки придумали не потому, что в реакте по-другому нельзя, а потому, что это удобно и имеет преимущества. Об этом говорит и существование байндингов того же redux под многие фреймворки.
В мире есть десятки годных вариантов, позволяющих разделить логику проекта на основе React.js на более логичные структуры данных: flux, reflux, redux, mobx — это только то что я сам знаю.
Статья сильно напоминает попытку ввести наивных людей в заблуждение насчёт React.js.
Почему?
По моему когда сравнивают:

scala

Даже если во втором случае добавить переносов перед каждым атрибутом, то всё-равно число строк меньше получится. Безотносительно данного примера, объём кода, необходимого для реализации одного и того же — не маловажная характеристика.
К слову сказать, формат 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 (какой псих такое вообще в прод потащит?)
Какая-то реклама собственного фреймворка, на основе унижения React-а
.
Участвую в проекте разработки CRM, пишем на React-е
, есть огромная библиотека простых виджетов/ui-компонентов, которые используются/переиспользуются вполне благополучно. Да, тем кто раньше писал только ООП сложно поменять парадигму и перейти на функциональных подход к компонентам, поэтому все еще используют наследование и перегрузку методов, вместо HOC
, но это лечится)
Уточню сразу, не сарказм и не наезд, вполне искренний интерес.
Отдельных верстальщиков нет, точнее есть, но они в других проектах, а там уже не известно используют они React
или нет. На нашем проекте вставал вопрос о разделении на верстальщика и разработчика фронта, но потом поняли, что это избыточно.
функциональных подход к компонентам
У меня язык не поворачивается назвать компонентой глупый кусок шаблона. Подход реакта требует нарушать инкапсуляцию для достижения гибкости. В результате "компоненты" получаются либо "дубовые", которые сложно адаптировать под разные контексты, либо с развесистыми конфигами, выпячивающими наружу всю внутреннюю кухню, без знания которой ты даже отрендерить компонент не сможешь.
Хорошая попытка. Но — нет. Walmart, например. http://www.electrode.io
Такой легкий компонент очень удобен при создании простых веб-страниц. Однако, когда требуется взаимодействие между несколькими компонентами, неизбежна передача функций обратного вызова (callback functions) в качестве параметра. В частности, для веб-страниц со сложными структурами приходится использовать десятки взаимосвязанных компонентов, в которых коллбэки передаются от родителей к потомкам из слоя в слой. Единственным результатом применения фреймворка React в таких сложных интерактивных веб-проектах будет то, что код станет слишком беспорядочным и трудноподдерживаемым.
Flux? Не, не слышал.
К сожалению, поддержка HTML в React является неполной. Разработчик должен вручную заменить class на classname, а for на htmlFor. Кроме того, синтаксис встроенных стилей необходимо поменять с CSS на JSON.
styled-jsx? Не, не слышал.
Минимальным блоком для повторного использования в React является компонент (React.Component). Он более лёгкий, чем Controller и View в AngularJS
В ангуляре тоже уже компонент давно есть, а во втором он идет как ключевой элемент работы.
Я вот не фанат реакта, но после оценки всего, что сейчас есть, выбрал именно его для создания сложного интерактивного приложения (причем это ГИС).
а во втором он идет как ключевой элемент работыУгу, только никак не расширяется, так как нельзя отрендерить другой компонент в качестве хоста, добавив пропсов. «Добавить» пропсы вообще нельзя, так как нет спредов. Или нельзя отнаследоваться, так как метаданные декоратора не наследуются. Или нельзя создать HOC.
Так что «компоненты» там так себе, одно название.
Почему не выбрали VueJs?
приложение на фреймворке 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мс). Итого — четверть секунды на первичный рендер. По пункту меню можно кликнуть и он становится текущим. Что происходит при переключении между записями?
Подход реакта. Заново формируем то же самое дерево (+50мс), заново фильтруем его с тем же результатом (+150мс), заново преобразуем в список пунктов меню (+50мс). Если мы не поленились реализовать sCU или делать всё на иммутаблах (а обычно об этом вспоминают, когда пользователи начинают жаловаться, что у них всё тормозит), то хотя бы не будет генерироваться virtual-dom для всех пунктов меню, а только для изменившихся (-40мс). После чего реакт делает дифф с real-dom и применяет разницу (+40мс). Итого — четверть секунды на удаление атрибута у одного элемента и добавление его другому.
- Эффективный подход. Удаляем атрибут у одного элемента (+1мс), добавляем его другому (+1мс). Даже если мы накинем сюда 50мс на обеспечение реактивности, то это всё равно будет в 5 раз эффективней подхода с виртуальным домом.
Числа взяты с потолка для иллюстрации того, что "рендер VDOM дешёвый" только лишь в простейших случаях, а sCU — не серебрянная пуля, решающая все проблемы с производительностью. Тут нужно промежуточное кеширование и своевременная инвалидация кеша на уровне данных, а не только на уровне рендеринга. А если у вас уже есть механизм обеспечивающий реактивную архитектуру, то виртуальный дом тут нужен как собаке пятая нога.
у вас есть список в несколько тысяч записей (а у некоторых пользователей — десятков тысяч)Ума не приложу, кому может понадобиться единовременно рендерить такой список. Это ж какой должен быть монитор, чтобы это все влезло. Рендерить можно только видимую часть и буферную область в обе стороны списка, чтобы не тратить время и не жрать память.
Тут речь про исходные данные. Исходных данных может быть и куда больше. Рендерить вы можете хоть вообще только один пункт меню, что не избавляет вас от необходимости делать полную обработку данных от начала и до конца, чтобы понять какой именно пункт меню надо отрендерить.
"Моя хата с краю" — позиция удобная, но не продуктивная. Если вы реализуете кеширование обработки данных и своевременную актуализацию этого кеша, то костыли с виртуальным домом вам будут не нужны, так как состояние реального дома от промежуточного кеша ничем принципиально не отличается.
Я почитал комментарии и заметил, что с вами вести адекватную беседу сложно, так как вы прыгаете с темы на тему. У нас есть проблема, большой список. Ниже вам описали, как решить ее. Вы тут же переходите на обработку данных.
Я бы с радостью обсудил проблемы 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, только вручную.
Гранулярность этого чека гораздо выше (мы чётко знаем где у нас могло что-то поменяться). И нет, вручную это проверять не надо. Вычислили новое значение, библиотека сравнива его со старым, и если зафиксировано изменение — каскад обновлений продолжается.
Например, из массива чисел подготовить данные для графика;
Как раз недавно этим занимался. У меня получилась такая последовательность:
- Каждый график принимает список чисел и по нему генерирует список точек.
- По списку точек вычисляются габариты.
- Диаграмма пробегается по всем графикам, запрашивает их габариты и формирует общий габарит.
- По визуальным и дата-габаритам вычисляется масштаб и смещение.
- Каждый график берёт масштаб и смещение и по ним вычисляет визуальные координаты точек.
- Из него формируется список координат, где каждая точка отстоит от другой минимум на N пикселей.
- Из координат формируется path строка, которая вставляется в соответствующий атрибут.
При этом, изменение данных одного графика не приведёт к перерендеру всех, если общие габариты не изменились.
С графом зависимостей — при модификации будет оптимальное изменение, при пересоздании — будет полная перестройка графа. Обновление ДОМ — оптимальное при модификации, полное перестроение в противном случае.
С чего бы полное перестроение? У реакта реконциляция работает лишь на одном уровне, да и то, если имя тега не поменялось. А если вдруг поменялось — всё поддерево будет уничтожено и создано вновь. В случае $mol_view каждый компонент имеет ссылку на соответствующий ему элемент. И куда бы вы его не переместили — элемент будет туда перемещён вместе со всем своим поддеревом. Например, вы можете перетащить виджет из одного блока в другой и на это и уйдёт ровно 2 операции с домом — удаление в одном месте и добавление в другой. А в некоторых случаях хватит и только второй операции.
Пересоздание графа зависимостей же — это самая затратная операция из всех, и если ее делать постоянно, то выигрыша в производительности не будет.
Построение графа, конечно, не бесплатно, но и не так уж затратно, а профита даёт много:
- Независимость от размеров приложения — обновления идут всегда по короткому предсказуемому пути.
- Мы всегда знаем какие объекты нам нужны, а какие можно безболезненно удалить из памяти.
- Мы всегда можем посмотреть что от чего зависит.
который дает аккуратное и предсказуемое (а это не менее важно часто, чем быстрое) обновление реального ДОМ дерева.
Ну вот алгоритм реконциляции в реакте я бы не назвал ни аккуратным, ни предсказуемым. Затесалась где-то лишняя обёртка или другое имя тега и привет, полный ререндер.
Гранулярность этого чека гораздо выше (мы чётко знаем где у нас могло что-то поменяться). И нет, вручную это проверять не надо. Вычислили новое значение, библиотека сравнива его со старым, и если зафиксировано изменение — каскад обновлений продолжается.
Понятно, что вручную обычно быстрее, т.к. учитывает специфику данных. Только вручную можно в любом фреймворке, это не интересно :)
С чего бы полное перестроение?… В случае $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.
Гораздо удобнее работать с полноценными объектами :)
Костыльна не мемоизация, а то как она прикручена. Вместо того, чтобы использовать граф зависимостей для точечных обновлений зависимых состояний, мы будем герерировать дом заново на каждый чих, но прикрутим к этому реконциляцию, чтоб не тормозило, будем вычислять дом из исходных данных на каждый чих, но прикрутим реселект (который добавляет тот же граф зависимостей), чтобы не тормозило, будем держать в сторе все когда-либо загруженные данные, но… а для этого какой-нибудь костыль уже придумали, чтобы приложение не пыталось засосать в память всю базу данных с сервера при продолжительной работе?
Проверки на изменение входных параметров контейнера завернуты в redux connect — та же мемоизация. Проверки входных параметров компонента можно проводить в scu — та же мемоизация. То, что вам почему-то это кажется костылем, вызывает лишь недоумение.
@raveclassic, потому что это следствие того, что redux архитектура заставляет пересчитывать одно и тоже ввиду того, что в иммутабельных структурах при изменении чего бы то ни было снизу, необходимо менять и все вышестоящие обёртки. А т.к. никакого графа зависимостей нет, то и, в случае react, зависимые react-компоненты вынуждены на всякий случай проводить rerender virtual-dom-а. а т.к. для redux жизненно необходима нормализация данных, то и разные сложные вычисления выпадают сюда же. И без мемоизации оно всё приедет не успевши завестись. Да вы и сами всё это знаете. По сути pureComponent в React это та же мемоизация.
После библиотек с графом зависимостей это и вызывает недоумение. Но правда лишь до тех пор, пока не приходит осознание, что другие подходы несут как преимущества, так и недостатки.
Переписал примерчик выше на селекторах:
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
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), избыточный трафик с сервером и тп.
Пара примеров из жизни:
В одном проекте было иерархическое меню и приходило он в виде развесистого JSON. Сервер парился со сборкой этого дерева, клиент парился с его разборкой. В какой-то момент решили позволить одному ребёнку иметь несколько родителей и понеслось: сервер отваливается по таймауту пытаясь собрать из 1000 записей гигантское дерево, трафик вырос на порядок ибо дерево стало весить десятки мегабайт, а если дело всё же доходило до клиента, то он пережёвывал это дерево не одну секунду. Решение простое и очевидное, для более-менее опытного архитектора, — выдавать данные в нормализованном виде (коллекции записей с перекрёстными ссылками). И (внезапно) код сервера упростился до "выплюнуть кусок таблицы из базы данных", трафик упал до исходных значений, код клиента избавился от рекурсий.
В другом проекте ребята решили программу тренировок слать одним большим документом и как есть класть его в монгу. И всё бы хорошо, если бы их достаточно было лишь создавать и показывать. Но хотелось и редактировать, причём, совместно. В результате мало того, что трафика было много (от чего в том числе и задержки при синхронизации), так ещё и мёржилось оно как попало, а хотелось выводить красивые сообщения "Ваш тренер перенёс ноги со вторника на среду". Там были и другие связанные с этим проблемы, но я их уже не помню. Правильным решением была бы, опять же, нормализация с разделением документа на отдельные сущности, даже если бы связи между ними были бы 1-к-1.
- В третьем проекте сразу делали нормализованно. Модель запрашивала лишь те поля, что ей нужны в данный момент. Сервер возвращал типизированные коллекции записей с перекрёстными связями. Простым универсальным адаптером, ответ сервера переводился в модели. Модель могла работать как на клиенте (через 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_mem_key()
Task_row( id : string ) {
const row = new $my_task_row
row.title = ()=> this.task_title( id )
return row
}
task_title( id : string ) {
const task = this.task( id )
return `${ task.title } (${ task.created } )`
}
Да, что-то вроде:
manager() {
return this.domain().manager( this.json().manager )
}
Я позволю себе "вклиниться" по поводу сравнения фреймворков и $mol. Почитал про него Ваши статьи — выглядит многообещающе благодаря tree, но это… революция? Как истинный конформист подожду success stories )) Пока что из всех решений для безобразий в браузере мне предпочтительней Elm. Он кажется игрушечным, но на деле перформанс в 2 раза выше в сравнении с реактом, не надо мучатся с вебпаками-бабелями, персистентные структуры данных из коробки и куча ништяков ещё.
Судя по бенчмаркам, применение изменений к дому у него не очень оптимальное. А вот добавление новых элементов реально шустрое.
Использовать объект и хранить список айдишников отдельно? Такой паттерн в документации Redux описан. React тут вообще не причем.
Компоненты React трудно использовать повторно в сложных веб-проектах.
Говоря о легком переиспользовании компонентов построенных с помощью полноценных архитектурных фраймворках, мы не берем во внимание что компонент является фасадом для маленькой-полноценной программы.
В случаи с реакт-компонентом это всего-лишь один маленький кирпичик, который если и будет являться частью некой архитектуры, то скорее всего она не будет заточена под переиспользование. По крайней мере мне известны лишь архитектуры для реакта, а не с использованием реакта. Ведь у того же angular именно компонент или ранее директива является точкой входа.
Исходя из этого было бы не оскорбительным сравнения компонентов архитектурных фраймворков с букетом, а реакт-компонентов с маленькой клумбой.
Я только и занимаюсь разработкой очень сложных интерактивных интерфейсов, поэтому могу не кривя душой сказать что реакт не предел мечтания, но у меня не хватило бы фантазии провести параллель между реактом и асинхронных запросов к серверу. И раз уж заговорили о асинхронности, которая видимо сложно кому-то дается, то лично для меня она не представляет какой-либо сложности, так как для меня этот диагноз врожденный.
Что касается известных мне архитектур, то как наверное большинство я начинал с обычной архитектуры на подобие mvc своими руками, затем перешел на redux, не всегда был им доволен и часто ворчал. В последнем, очень сложном проекте, я встал перед выбором либо вообще отказаться от redux либо поностью нарушить все его принципы. Я выбрал первое и это избавило меня от проблем которые и привели к отказу, но сам не верю что говорю эти слова, именно после отказа я по настоящему полюбил redux. Мне было с ним не всегда хорошо, но и без него мне лучше не стало.
Правда для 1-го aпреля.
Фреймворк, HTML-шаблоны
Явно автор знает, о чем поветсвует
Сравните лучше с VueJS, то что риакт не нужен люди и так уже начинают понимать.
И сложных проектов на реакте уже полным- полно: тинькоф, сбербанк, почта России и т.д
То ли автор не осилил реакт, то ли просто пытается пиарить scala фреймворк, не пойму
Я являюсь большим любителем как scala, так и scala.js. Однако считаю, что данная статья — это худшее, что можно было выбрать для перевода. Я думаю, что автор статьи просто попытался как-то привлечь внимание к собственной разработке и выбрал темный путь: засрать популярную в js мире библиотеку, чтобы у огромной аудитории бомбануло и она пошла в комменты. И как побочный эффект — эта аудитория всё-таки узнает о Binding.scala.
Касательно самой статьи уже всё в общем-то написали. Повсеместное манипулирование фактами, умалчивание очевидных вещей и указания на сильные стороны Binding.scala.
Эти люди, кажется, вообще не понимают зачем сделан реакт. Реакт сделан что бы писать меньше if'ов когда выбираешь какой шаблон отрендерить следующим. Всё остальное — боль везде и зависит от таланта программиста.
Больше, чем React: Почему не следует использовать ReactJS для сложных интерактивных фронтенд-проектов