Перевод статьи, посвящённой использованию ReactJS для создания фронтенда.

React — отличный инструмент для реализации простых интерактивных веб-сайтов. Но насколько он применим в сложных фронтенд-проектах? Работает ли он там столь же хорошо?

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

Ключевые моменты


  • Компоненты React трудно использовать повторно в сложных веб-проектах.
  • Алгоритм Virtual DOM в React медленный и неточный.
  • HTML-шаблоны React не являются ни полными, ни мощными.
  • React требует сложного асинхронного программирования при общении с сервером.
  • Binding.scala содержит меньше концепций, но больше функций. Перспективный Binding.scala решает сложные проблемы, которые React решить не может.

Предпосылки


Многим фреймворк React кажется более простым и удобным по сравнению с AngularJS. Одной из самых полезных его функций является связывание данных (data binding). Оно позволяет сопоставить источники данных с элементами веб-страниц, что предоставляет удобный способ реализации простых интерактивных веб-сайтов.

Однако возможны ситуации, в которых React не может решать запутанные проблемы так же легко, как простые. Если поэкспериментировать с TodoMVC, то окажется, что приложение на фреймворке Binding.scala содержит всего 154 строки кода по сравнению с 488 строками на React.



Дальше мы рассмотрим четыре проблемы React и как их решает Binding.scala.

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


Минимальным блоком для повторного использования в React является компонент (React.Component). Он более лёгкий, чем Controller и View в AngularJS. От веб-разработчика требуется лишь реализация функции render, которая транслирует свойства (props) и состояние (state) компонента в HTML-элементы.

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

Проблема 2. Virtual DOM в React является медленным и неточным


Алгоритм рендеринга в React использует Virtual DOM.

Разработчик обязан предоставить функцию render, которая создает виртуальный DOM, используя свойства и состояние компонента. А React на основе этого виртуального DOM конструирует реальный.

При изменении состояния React вновь вызывает функцию render и строит новый Virtual DOM. Затем он анализирует различия между старой и новой версией виртуального DOM и применяет их к реальному DOM.

У этого процесса есть два недостатка:

  1. 1. Независимо от того, что изменилось в состоянии, функции рендеринга всегда будут генерировать новые полные виртуальные DOM. Если функция render сложна, то вычислительные ресурсы тратятся впустую.
  2. 2. Сравнение двух версий DOM медленное и подвержено ошибкам. Например, если вы хотите вставить элемент li в начало ul, то React ошибочно решит, что вы модифицировали все компоненты li в ul и добавили один li в конце.

Поскольку две версии виртуального DOM независимы друг от друга, React не имеет представления о том, что происходит на самом деле. Он случайным образом угадывает произошедшие изменения, основываясь на обоих DOM. Этот алгоритм очень медленный и неточный. Веб-разработчики вынуждены использовать свойство key, а также методы shouldComponentUpdate и componentWillUpdate, чтобы помочь фреймворку угадать правильно.

Проблема 3. HTML-шаблоны React не являются ни полными, ни мощными


Для разработки HTML-шаблонов React поддерживает JSX.

Теоретически, фронтенд-разработчики могут превратить статический HTML в динамическую веб-страницу, просто скопировав HTML в JSX-файлы и добавив немного дополнительного кода. Действительно, React больше подходит для повторного использования HTML-шаблонов по сравнению с другими фреймворками, такими как Cycle.js, Widok и ScalaTags.

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

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

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

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


Рассмотрим работу React с сервером в терминах шаблона Model-View-ViewModel. Веб-разработчику нужно реализовать слой доступа к базе данных (Model), state в качестве ViewModel и функцию render в качестве View. Модель обеспечивает доступ к API бэкенда и устанавливает state (ViewModel), используя промисы (Promise and fetch API). Затем render (View) обеспечивает визуализацию ViewModel на веб-страницах.

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

Выводы


Binding.scala в некотором роде кажется похожим на React. Но лежащий в его основе механизм полностью отличается от React и Widok, он проще и универсальнее. Binding.scala гибче и мощнее, чем React. Мы можем использовать Binding.scala для решения сложных проблем, которые React решить не может.

Кроме четырех аспектов, упомянутых выше, есть и довольно значительная проблема управления состоянием в React. Если для управления состоянием использовать стороннюю библиотеку, такую как Redux, то структура приложения станет еще более запутанной, а количество слоев увеличится.

Для сравнения, Binding.scala описывает сложное состояние, используя тот же механизм связывания данных (data-binding), что и для рендеринга веб-страницы. И нет никакой надобности в сторонних библиотеках для обеспечения таких функций, как клиент-серверное взаимодействие, управление состоянием и URL-диспетчеризация.

Различия между Binding.scala и React:

Binding.scala React
Повторное использование Минимальный блок для повторного использования Метод Компонент
Уровень сложности повторного использования Возможность повторного использования независимо от интерактивных или статических компонентов Простота повторного использования в статических компонентах, но трудно использовать в интерактивных компонентах
Алгоритм рендеринга веб-страницы Алгоритм Точная привязка данных Virtual DOM
Производительность Высокая Низкая
Корректируемость Автоматически обеспечивает правильность Необходимо вручную настроить ключевые атрибуты
HTML Синтаксис Scala XML JSX
Поддерживает синтаксис HTML или XHTML? Полностью поддерживает XHTML Частично поддерживает, не может компилировать нормальный XHTML. Разработчики должны вручную заменить атрибуты class и for на className и htmlFor, а также изменить синтаксис стилей с CSS на JSON
Как проверяется синтаксис Проверяется автоматически при компиляции Выполняется через propTypes, но не может найти очевидные орфографические ошибки
Взаимодействие с сервером Механизм Автоматическая удаленная привязка данных MVVM + асинхронное программирование
Уровень трудности реализации Легко Тяжело
Другое URL-диспетчеризация Поддерживает URL-адреса как обычные связанные переменные, не требует сторонней библиотеки Не поддерживает, требуется сторонняя библиотека react-router
Полнота функциональности Полное решение для фронтенд-разработки Содержит только функциональность слоя View. Требует сторонние библиотеки для полной фронтенд- разработки
Кривая обучения API относительно прост. Легок для понимания даже теми, кто никогда не использовал Scala Удобно. Но чрезвычайно трудно изучить стороннюю библиотеку, которая используется для компенсации слабых сторон фреймворка

Ещё недавно среди представителей сообщества Scala.js самым популярным фронтенд-фреймворком был Widok. Но вскоре это место занял Binding.scala. Даже автор Widok Тим Нирадзик (Tim Nieradzik), не смог удержаться от похвалы и назвал Binding.scala наиболее перспективным фрейворком для рендеринга HTML5. Awesome Scala, сравнивая Binding.scala с конкурентами, также приходит к выводу, что этот фреймворк сейчас популярнее, чем Udash и Widok



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

Комментарии (166)


  1. ihost
    24.03.2017 13:28
    +14

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

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


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

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


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

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


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


    1. Vestild
      24.03.2017 14:13
      +3

      Если нужна статическая проверка, можно использовать Flow
      А лучше вообще на TypeScript перейти


      1. VolCh
        26.03.2017 13:38

        Чем лучше?


        1. raveclassic
          26.03.2017 19:37

          <sarcasm>Потому что он не тайп-чекер.<\sarcasm>


    1. comerc
      24.03.2017 15:26
      +1

      Продолжительные по времени действия… еще лучше реализовать всю архитектуру через events — в таком случае явные асинхронные вызовы вообще не нужны

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


    1. serf
      25.03.2017 04:04
      +2

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

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


      1. bjornd
        25.03.2017 09:25
        +3

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


      1. vintage
        25.03.2017 10:16
        +2

        TypeScript дружит с React.


  1. http3
    24.03.2017 13:30
    -15

    Я всегда говорил, что React — говно.
    jQuery наше все :)


    1. Hazrat
      24.03.2017 13:55
      +8

      Так говорят те, кто не хочет разбираться в этой каше из ES6/Фреймворках/сборщика/nodejs/npm/командной строке и остальном. Достаточно просто углубиться и понять, что фронтенд за последние несколько лет ушел далеко вперед, и на сколько отстали люди с jquery.


      1. DrPass
        24.03.2017 14:40
        +17

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


        1. kekekeks
          24.03.2017 15:17
          +4

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


        1. Hazrat
          24.03.2017 15:53

          Если речь идет о большом проекте, то сами понимаете, поддерживать его достаточно трудно, если это все в одном файле js. Уже много было статьей, о преимуществах компонентного подхода разработки, так вот, вносить правки в проект, где например react+redux достаточно удобно.


          1. http3
            31.03.2017 11:10
            +1

            если это все в одном файле js

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

            Ниасиляторы.


        1. wanhelsing
          26.03.2017 20:54

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


          1. DrPass
            26.03.2017 22:15

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


            1. wanhelsing
              27.03.2017 12:36

              И как же интересно вы делаете адаптивность на десктопе? Максимум наверно тянете кнопки и инпуты на всю ширину. В вебе же нужно полностью перекомпоновать интерфейс, если его открывают на мобилке. Оффлайн режим, SEO, непредсказуемость доступных API, непредсказуемые платформы (сорян, упаковать свежий .NET Framework в инсталлятор не получится) и т.д.


              1. DrPass
                27.03.2017 13:59
                +1

                И как же интересно вы делаете адаптивность на десктопе?

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

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

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

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


      1. MorozoW
        24.03.2017 17:36

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


      1. http3
        31.03.2017 10:43

        фронтенд за последние несколько лет ушел далеко вперед

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

        В чем же?

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


        1. vintage
          31.03.2017 11:12

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


          1. http3
            02.04.2017 20:24

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


            1. VolCh
              02.04.2017 20:26

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


            1. vintage
              02.04.2017 23:07
              +1

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


              1. VolCh
                03.04.2017 06:19
                +1

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


                1. vintage
                  03.04.2017 07:34
                  +1

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


                  1. VolCh
                    03.04.2017 08:17

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


                    1. vintage
                      03.04.2017 09:23

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


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


                      1. VolCh
                        03.04.2017 11:45
                        +1

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


                        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/


                        1. vintage
                          03.04.2017 12:23

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


                          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
                          }

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


                          1. VolCh
                            03.04.2017 13:05
                            +1

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


                            1. vintage
                              03.04.2017 15:23

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


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


                              1. VolCh
                                03.04.2017 16:32

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


                                1. vintage
                                  03.04.2017 17:09

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


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


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


                                  1. VolCh
                                    03.04.2017 17:35

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


              1. http3
                03.04.2017 10:13

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

                Тогда давайте вернемся к корневому комменту:

                Я всегда говорил, что React — говно.
                jQuery наше все :)


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


                1. vintage
                  03.04.2017 11:35

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


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


                  1. http3
                    03.04.2017 16:08

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

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


                    1. vintage
                      03.04.2017 17:12
                      +1

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


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


    1. iborzenkov
      24.03.2017 14:19
      -6

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


      1. Miraage
        24.03.2017 14:31
        +2

        Angular 2 наше все :)

        Angular 4 версии уже.


        1. Alex_Crack
          24.03.2017 17:36
          +1

          Тогда уж просто Angular.


    1. msatersam11
      24.03.2017 15:30

      Годный вброс:)


    1. http3
      25.03.2017 12:49
      -1

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


  1. k12th
    24.03.2017 14:11
    +11

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


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

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


    1. Miraage
      24.03.2017 14:39
      +1

      Уже прогресс у ребят. (src)

      It's disappointing that we reach only 2.7 MB this way, but it's definitely better than 16.



      1. k12th
        24.03.2017 14:44
        +3

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


    1. vintage
      25.03.2017 10:24

      На сколько я понимаю, никто не заставляет тащить с собой весь рантайм. Тот же ToDoMVC весит 300кб, что даже меньше, чем реализация на реакте.


      1. bjornd
        25.03.2017 10:58
        +1

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


        1. vintage
          25.03.2017 11:51

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


  1. kpakozz96pyc
    24.03.2017 14:31

    Казалось бы, причем здесь Angular?


    1. Veikedo
      24.03.2017 15:14
      +2

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


  1. PycBouH
    24.03.2017 14:32
    +12

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

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


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

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

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


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

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


    Автор, кажется, даже не открывал документацию и совсем не понял, что такое JSX и зачем оно нужно. Эх…


    1. Miraage
      24.03.2017 14:45
      -1

      А еще очень смешно читать про никзую производительность React, в то время, когда его Virtual DOM порой даже быстрее нативного JS. Молчу про Fiber.


      1. Aingis
        24.03.2017 18:55
        +4

        в то время, когда его Virtual DOM порой даже быстрее нативного JS.

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


        1. raveclassic
          24.03.2017 19:10

          Тут имелись в виду ручные обновления DOM без синхронизации по RAF


          1. Aingis
            24.03.2017 20:08
            +2

            Ручные обновления DOM — это как раз на нативном JS. А?тут утверждается, что быстрее. Вот мне и интересно, как это.


            1. raveclassic
              24.03.2017 22:39
              -1

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


              1. Aingis
                25.03.2017 06:37

                Не подменяйте понятия. Нативный JS не отменяет необходимости писать эффективный код.


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


                1. raveclassic
                  25.03.2017 11:15

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

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

                  Нативный JS не отменяет необходимости писать эффективный код.
                  Точно так же если тупо писать на Реакте «в лоб»
                  Все верно, но это совсем не про vdom.


                  1. Aingis
                    25.03.2017 11:52

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

                    Вот я и говорю: не выдумывайте. miraage ничего про буферизацию не говорил.


              1. vintage
                25.03.2017 10:36

                множественные разовые обновления буферизуются по фрейму

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


                1. raveclassic
                  25.03.2017 11:09
                  +1

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

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


                  1. vintage
                    25.03.2017 11:55

    1. vintage
      25.03.2017 10:43
      +1

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

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


      1. PycBouH
        25.03.2017 13:58

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


        1. vintage
          25.03.2017 14:49

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


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


          1. PycBouH
            25.03.2017 15:28
            +1

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

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

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


            1. vintage
              25.03.2017 15:52

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


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

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


              1. PycBouH
                25.03.2017 17:16

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

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


                1. vintage
                  25.03.2017 17:23

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


                  1. PycBouH
                    25.03.2017 17:50
                    +1

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


              1. indestructable
                28.03.2017 01:23

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


                1. vintage
                  28.03.2017 08:15

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


                  1. indestructable
                    28.03.2017 12:20

                    Ну то есть


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


                    1. vintage
                      28.03.2017 16:13

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


    1. pawlo16
      25.03.2017 15:56

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


      1. PycBouH
        25.03.2017 17:10

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

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

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


      1. indestructable
        28.03.2017 01:30

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


        1. vintage
          28.03.2017 08:16
          +1

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


          1. raveclassic
            28.03.2017 09:25

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


            1. bjornd
              28.03.2017 09:27
              +2

              Он не притворяется


  1. saggid
    24.03.2017 14:32
    +7

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


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


  1. devlev
    24.03.2017 14:36
    +1

    Вот после сравнения кол-ва строк кода хочется просто не читать дальше.


    1. vintage
      25.03.2017 10:45

      Почему?


      1. devlev
        26.03.2017 00:52
        +1

        А вы сами смотрели что сравнивают?
        По моему когда сравнивают:

        вот так
        React
        image

        scala
        image


        1. vintage
          26.03.2017 03:21
          -3

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


          К слову сказать, формат 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


          1. bjornd
            26.03.2017 11:41
            +1

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

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

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


            1. vintage
              26.03.2017 11:48
              +1

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


              1. yogurt1
                02.04.2017 23:30
                +1

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


                1. vintage
                  03.04.2017 00:05

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


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


  1. ddn123
    24.03.2017 14:45
    +4

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


  1. ThisMan
    24.03.2017 14:52
    +6

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


    1. kernel72
      24.03.2017 15:17

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


    1. j_wayne
      24.03.2017 15:18

      У вас есть верстальщики? Как они относятся к JSX?
      Уточню сразу, не сарказм и не наезд, вполне искренний интерес.


      1. zxcabs
        24.03.2017 17:16
        +1

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


        1. raveclassic
          24.03.2017 19:14
          +1

          Для таких случаев есть storybook — вот это действительно удобно.


      1. ThisMan
        24.03.2017 18:19

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


    1. vintage
      25.03.2017 11:03

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

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


  1. comerc
    24.03.2017 15:08
    +1

    Хорошая попытка. Но — нет. Walmart, например. http://www.electrode.io


    1. vintage
      25.03.2017 10:50

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


  1. comerc
    24.03.2017 15:12

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

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


  1. comerc
    24.03.2017 15:17
    +1

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

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


    1. raveclassic
      24.03.2017 19:14
      +1

      Господи, да просто css-modules.Не разглядел инлайн


  1. SergeyVoyteshonok
    24.03.2017 15:59

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

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

    Я вот не фанат реакта, но после оценки всего, что сейчас есть, выбрал именно его для создания сложного интерактивного приложения (причем это ГИС).


    1. raveclassic
      24.03.2017 19:29

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

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


    1. serf
      25.03.2017 02:43

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


      1. SergeyVoyteshonok
        25.03.2017 11:42
        +2

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


        1. serf
          27.03.2017 21:08

          отсутствие jsx

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


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

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


  1. bjornd
    24.03.2017 16:15
    +4

    приложение на фреймворке 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. Про разнообразие сторонних компонентов для обеих платформ вообще молчу.


    1. pawlo16
      24.03.2017 21:47
      +2

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

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


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

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

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

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


      Решение: Flow

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


      1. yogurt1
        24.03.2017 22:13

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


        1. vintage
          25.03.2017 11:39

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


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


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

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


          1. raveclassic
            25.03.2017 11:46

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


            1. vintage
              25.03.2017 11:58

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


              1. raveclassic
                26.03.2017 02:16

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


                1. vintage
                  26.03.2017 03:26

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


                  1. yogurt1
                    26.03.2017 10:31
                    +2

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


                    1. vintage
                      26.03.2017 11:15
                      +1

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


          1. bjornd
            26.03.2017 14:47

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

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

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


            1. vintage
              26.03.2017 15:51
              +1

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

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


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

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


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

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


          1. indestructable
            28.03.2017 01:54
            +2

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

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


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


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


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


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


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


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


            1. raveclassic
              28.03.2017 09:32

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


            1. vintage
              28.03.2017 09:40

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

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


              Вот, например, описание этой проблемы и костыля к редуксу, для её решения: 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% приложений никогда не испытают проблем с производительностью, а половина тех, что испытают — решат их минимальными исправлениями.

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


              1. indestructable
                28.03.2017 12:37

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

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

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

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


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

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


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


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

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


                1. vintage
                  28.03.2017 16:22

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

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


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

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


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

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


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

                  Например?


                  1. indestructable
                    28.03.2017 17:20

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

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


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


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


                    Например?

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


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


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


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


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

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


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


                    1. bjornd
                      28.03.2017 17:34

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

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


                      1. indestructable
                        28.03.2017 23:20

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


                        1. raveclassic
                          28.03.2017 23:36

                          Привет, redux!


                          1. VolCh
                            29.03.2017 10:10

                            А причём тут он?


                            1. raveclassic
                              29.03.2017 23:05

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


                    1. vintage
                      29.03.2017 01:14

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

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


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

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


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

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


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

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


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

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


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

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


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

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


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

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

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


                      1. indestructable
                        29.03.2017 10:22

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

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


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

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


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

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


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


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

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


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


                        1. vintage
                          29.03.2017 14:46

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

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


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

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


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


                          1. VolCh
                            29.03.2017 17:51

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

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


                            1. vintage
                              29.03.2017 20:06

                              О чём и речь, да :-)


                          1. raveclassic
                            29.03.2017 23:08
                            +1

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


                            1. vintage
                              30.03.2017 08:08

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


                              1. raveclassic
                                30.03.2017 10:30
                                +1

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

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


                                1. faiwer
                                  30.03.2017 11:22

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


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


                                  1. raveclassic
                                    30.03.2017 14:52

                                    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


                                    1. raveclassic
                                      30.03.2017 15:09

                                      faiwer Прошу прощения за обман. Селектор app в user нужно, конечно же, заменить на app => app.user.entities, чтобы user срабатывал только на изменение entities. Соответственно, user будет выглядеть вот так:

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


                                      1. faiwer
                                        30.03.2017 15:29
                                        +1

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


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


                                    1. faiwer
                                      30.03.2017 15:21

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


                          1. indestructable
                            29.03.2017 23:18

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

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


                            Ну а "нетривиальных структур данных" в модели быть не должно. Гораздо удобней работать с нормализованными данными, а не с развесистым 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. Это не совсем то, что у вас, но позволяет указать, когда элемент должен переиспользовать реальный ДОМ элемент (но опять же, я не уверен).


                            1. vintage
                              30.03.2017 09:40

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

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


                              Те же лишние дивы-врапперы, появляющиеся по условию, которые вы упоминали выше. Замечу, однако, что в 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 действует только в пределах одного виртуального элемента.


                              1. vintage
                                30.03.2017 09:46

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


                                rows() {
                                    return [ this.Head() , ... this.tasks().map( task => this.Task_row( task.id() ) ) ]
                                }


                              1. VolCh
                                30.03.2017 10:07

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

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


                                1. vintage
                                  30.03.2017 12:56

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


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


                                  1. VolCh
                                    30.03.2017 13:09

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


                                     return  domain.manager(this.managerId);

                                    ?


                                    1. indestructable
                                      30.03.2017 13:34

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


                                      1. VolCh
                                        30.03.2017 14:29

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


                                      1. vintage
                                        30.03.2017 20:08

                                        Чтобы можно было перегружать свойство целиком (и геттер и сеттер).


                                        Например:


                                        @ $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 } )`
                                        }


                                    1. vintage
                                      30.03.2017 20:01

                                      Да, что-то вроде:


                                      manager() {
                                          return this.domain().manager( this.json().manager )
                                      }


              1. pawlo16
                29.03.2017 15:35

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


                1. vintage
                  29.03.2017 17:18

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


          1. yogurt1
            28.03.2017 23:20

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


            1. vintage
              29.03.2017 01:22

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


  1. copal
    24.03.2017 17:37
    +1

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

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

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

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

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

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


  1. ElianL
    24.03.2017 19:03

    По-моему отличная статья!

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


    1. raveclassic
      24.03.2017 19:17

      Ааа, так сегодня ж пятница!


  1. yogurt1
    24.03.2017 20:02

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


  1. serf
    25.03.2017 02:46
    -3

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


  1. varanio
    25.03.2017 21:14
    +1

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

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

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


  1. LMnet
    27.03.2017 06:02
    +1

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