Нетрадиционный обзор React

http://www.letscodejavascript.com/v3/blog/2014/09/react_review
  • Перевод
Привет, Хабр!

Мы в Хекслете любим свой стек технологий :) Когда рассказываем другим — многие завидуют: Rails, Docker, AWS, React JS. Реакт мы используем в первую очередь для создания веб-среды для разработки Hexlet-IDE, которая позволяет нашим пользователям выполнять упражнение по разработке приложений и взаимодействию с виртуальной машиной прямо в браузере.

Сегодня мы публикуем перевод статьи «An Unconventional Review of React» Джеймса Шора, ведущего проекта Let’s Code: Test-Driven JavaScript.



Он мне понравился. Я не ожидал такого.

Для специальных выпусков Let’s Code JavaScript в августе и сентябре я изучал Реакт.

На случай если вам не знаком Реакт: это библиотека для фронт-энд веб-разработки. С помощью него создаются компоненты: короткие, не-совсем-ХТМЛ теги, которые можно комбинировать для создания интерфейса.

Реакт знаменит своими нетрадиционными решениями: реализацией виртуального DOM’а, созданием элементов интерфейса в JavaScript вместо шаблонов, создание суперсета языка JavaScript — JSX, который позволяет вставлять не-совсем-ХТМЛ прямо в код JS.

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

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

/** @jsx React.DOM */
// Copyright (c) 2014 Titanium I.T. LLC. All rights reserved. For license, see "README" or "LICENSE" file.
"use strict";

var StockMarketTableCell = require("./stock_market_table_cell.js");

var StockMarketTableRow = module.exports = React.createClass({
  render: function render() {
    var year = this.props.stockMarketYear;

    return <tr>
      <StockMarketTableCell value={year.year()} />
      <StockMarketTableCell value={year.startingBalance()} />
      <StockMarketTableCell value={year.startingCostBasis()} />
      <StockMarketTableCell value={year.totalSellOrders().flipSign()} />
      <StockMarketTableCell value={year.capitalGainsTaxIncurred().flipSign()} />
      <StockMarketTableCell value={year.growth()} />
      <StockMarketTableCell value={year.endingBalance()} />
    </tr>;
  }
});

В этом суть Реакта, и этим он отличается от других. Единственный вопрос: хорош ли он?

Критерии нетрадиционного обзора


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

В течение следующих 5-10+ лет, когда я буду поддерживать свой продукт, этот код принесет больше пользы или страданий?

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

Для этого у меня есть пять критериев.

1. Замкнутость (Lock-in). Когда я решу перейти на новый или более лучший фреймворк (или библиотеку), насколько сложно будет переключиться?

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

3. Побочная сложность (Accidental Complexity). Я трачу время на решение своей проблемы или борюсь с фреймворком?

4. Тестирование (Testability). Могу ли я просто тестировать свой код, без лишней мороки с mock-объектами?

5. Совместимость с поисковыми движками (Search Engine Compatibility). Придется ли танцевать с бубном чтобы заставить поисковики индексировать мой сайт?

Я оценил Реакт в каждой категории с помощью a (уиии!), (буэээ!), or ⚇ (ни туда, ни сюда).

1. Замкнутость — ⚇ (ни туда, ни сюда)


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

Но почему Реакт не получил печальную рожицу?

Два момента спасают Реакт. Во-первых, Реакт провоцирует помещать логику приложения вне UI, так что не придется переписывать все приложение.

Во-вторых, API Реакта относительно небольшое. Не так много точек соприкосновения (см. пункт 3). Это означает, что меньше вероятность сломать что-то при обновлении Реакта. Также, Реакт легко использовать изолированно, для отдельной части страницы, так что можно постепенно мигрировать с Реакта когда появится необходимость.

2.Упрямая архитектура — (уиии!)


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

Некоторые люди считают упрямую архитектуру хорошим признаком. «Это позволяет мне понять как структурировать код». Я предпочитаю обратное. Структура приложения должна диктоваться требованиями приложения. Фреймворк не может предсказать эти требования, и они будут меняться с развитием приложения.

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

3. Побочная сложность — ⚇ (ни туда, ни сюда)


О Реакте необходимо знать очень мало, и он мне показался простым и понятным. Есть лишь пара моментов (разница между «props» и «state»), лишь пара концепций (как управлять состоянием; иммутабельные рендер-методы) и несколько методов для реализации типичного компонента. Мой самый сложный компонент содержит аж три Реакт-метода. В большинстве случаев хватает одного render().

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

Сейчас, кажется, команда Реакта успевает все править. Но что будет через пять или десять лет, когда Реакт уже не самая модная штука? Браузера продолжают развиваться, и перед тем как выбрать Реакт для своего проекта, спросите себя: уверен ли я, что Реакт будет поспевать за всеми нововведениями, необходимыми моему приложению?

4. Тестирование — (уиии!)


История с тестированием Реакта выглядит немного незрелой. Она еле-еле дотянула до улыбающейся рожицы.

На первый взгляд Реакт дает простой API для тестирования, и кажется там есть все что нужно. Можно рендерить компонент и искать в дереве DOM с помощью хорошего набора функций. Можно симулировать события. Можно делать mock-компоненты, но я был доволен тем, что они мне ни разу не понадобились.

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

Например, компонент ApplicationUi содержит компонент StockMarketTable. Я хочу проверить обновляется ли таблица при изменении конфигурации. Для этого я хотел проверить таблицу с готовым хард-кодом:

it("updates stock market table when user configuration changes", function() {
  config.setStartingBalance(...);

  var expectedTable = <StockMarketTable stockMarketProjection={projectionFor(config)} />;
  var actualTable = TestUtils.findRenderedComponentWithType(app, StockMarketTable);

// но как делать сравнение?
});


В итоге я обошел проблему, закопавшись в приватные детали реализации Реката, рендерил компоненты в статичный ХТМЛ и сравнивал результат. Вот код.

function checkComponent(actual, expected) {
  var actualRendering = React.renderComponentToStaticMarkup(actual._descriptor);
  var expectedRendering = React.renderComponentToStaticMarkup(expected);

  expect(actualRendering).to.equal(expectedRendering);
}


Он работает, но в случае падения генерирует страшное сообщение об ошибке, и этот код зависит от деталей реализации Реакта. (Пришлось визуализировать объектный граф рантайма!) Возможно есть способ лучше — и должен быть способ лучше — но я его не нашел.

Кроме этой не такой уж мелкой проблемы, тестировать Реакт одно удовольствие. Зацените тесты ApplicationUi. Они чудесны в своей простоте, понятны и не содержать mock’ов. Ни один тест не содержит больше двух строк, и это самый сложный компонент всего приложения.

5. Совместимость с поисковыми движками — (уиии!)


Реакту удалось сделать небольшое чудо: они сделали фронт-энд фреймворк, не имеющий проблем в поисковиками.

Благодаря виртуальному DOM’у, Реакт-приложения могут рендериться на стороне сервера с помощью Node.js. Это означает что нужен лишь один канал рендера и для поисковиков и для реальных пользователей. Это также означает что страницы показываются сразу после их подачи. Никакого ожидания document ready событий или загрузки всего JS. Реакт можно использовать для подачи статичной разметки, без клиентского кода вообще.

Круто.

Итог: рекомендую.

Реакт на удивление хорошо собранная библиотека. Мне он показался простым, интуитивно-понятным и легким в использовании. Если вы ищете фронт-энд библиотеку для сложных приложений или даже для сложных виджетов для существующей страницы, стоит обратить внимание на Реакт. Он не идеален, и стоит задуматься о потенциальных проблемах с дырявой абстракцией DOM’а, но он очень хорош. Рекомендую.
Hexlet 52,04
Практические уроки по программированию
Поделиться публикацией
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее
Реклама
Похожие публикации
Комментарии 34
  • +4
    Немного отвлеченно, вставлять html разметку в код теперь считается круто?
    • +4
      Что значит «круто»?
      • +2
        {«круто»: «высокий стиль арихитектуры разработки»}
      • +7
        Вообще конечно вопрос интересный. Делать вещи в духе
         $('selector').append('<div>something</div>')
        
        не очень круто. А подход реакта — вполне. По крайней мере разработчики считают, что так как компоненты не столько меняют DOM, а просто генерируют дерево абстарктных объектов, то почему бы не засунуть код, возвращающий это самое дерево, прямо в код компонента? Вполне логичное решение, ИМХО.
        • –6
          Интересный момент в том, что в PHP, откуда, видимо, создатели и черпали вдохновение, уже много лет считается хорошей практикой не смешивать HTML и логику. Именно на этом настаивают сторонники дополнительных шаблонизаторов в PHP, который сам по себе неплохой шаблонизатор.
          • +11
            Ну насколько я себе понимаю то, что предлагает Реакт (по крайней мере я так стараюсь реализовывать код на нём в своих проектах), логика и так должна быть отделена от представления. Компонент занимается только переносом состояния в верстку. Вся бизнес-логика сидит отдельно.
            • +8
              Не путайте «бизнес логику» и «логику отображения». Некоторые в PHP комьюнити, знаете ли, до сих пор в базу из view лезут.
              • 0
                В императивной шаблонизации я сторонник подхода, при котором представление — это практически чистый HTML, код в котором ограничивается выводом значений переменных, циклами, ну, может, еще какими-нибудь элементарными вещами. Мне кажется, что много HTML — мало кода читается гораздо лучше и выглядит гораздо чище, чем много HTML и много кода. А уж о бизнес-логике в представлении и речи не было.
                • +1
                  Я полностью с вами согласен.
                • 0
                  При использовании lazy loading и прочих абстракций типа прокси и декораторов залезть в базу через какой-нить геттер вполне нормально, даже если она меняется при этом (например, пишется лог обращений к записи).
                  • 0
                    ну тут мы не в базу лезем, а забираем кусок состояния объекта, то что при этом залазит в базу что-то это уже мелочи. Вью от базы в этом случае не зависит.
            • +3
              JSX не является разметкой. А смысл функции render отдать virtual dom.
              • 0
                Не круто, но если это действительно становится проблемой (а до определённого предела это лишь академическое замечание), то я уверен, что можно вынести шаблоны в отдельные файлы и использовать шаг сборки: gulp, плагины browserify для инклуда файлов и т.п.

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

                Лично для меня это не является проблемой, потому что можно вообще не использовать JSX. По какой-то причине я не полюбил JSX, хотя до этого имел дело, например, с Ангуляром и Vue, которые постулируют похожий подход работы с разметкой. Использование чистого JS видится мне более гибким и компонуемым.
                • +2
                  Не заметил чтобы Ангуляр постулировал размещать разметку внутри кода, да такая возможность есть, но как правило на практике используют шаблоны в виде внешних html, плюс кешируют их заранее при сборке (во избежании доп. запросов).
              • +11
                Немного отвлеченно, вставлять html разметку в код теперь считается круто?

                Тут зависит от того, кто автор фреймворка. От facebook — это OK, создали новую парадигму, от Васи Пупкина — загнобили бы.
                • +9
                  К сожалению, отделять логику от представления в js очень сложно. Я несколько раз наблюдал раздутые шаблоны на каком-нибудь хенделбарс, с кучей хелперов, в итоге, в шаблоне получается адская смесь из: 1) html, 2) js 3) кода специфичного шаблонизатора.
                  facebook не стал бороться, просто убрали синтаксический шум из кавычек, переносов слов, запилили интерполяцию строк. Компоненты на много гибче и проще, чем с вынесенным отдельно шаблонизатором. Да и вообще, никто не мешает писать что-то типа:
                  React.createElement("div", null); 
                  
                  • +5
                    Полностью согласен. По факту вы генерируете DOM дерево. Никакого HTML в JS нет и в помине. JSX так же опционален, как и любой другой препроцессор([coffee,type,pure]script). Смущает XML-синтаксис в JS — пишите в объектной нотации, кто ж запрещает?
                  • 0
                    Угу, я на это не явно и намекал.
                  • +3
                    Автор ничего не сказал про переносимость компонентов реакта. Могу я один и тот же компонент, однажды написанный, использовать в разных проектах? Так, чтобы без копипасты?
                  • +1
                    По поводу server-side рендеринга, тут дело нодой не ограничивается, я например делал изоморфное приложение на Clojure + Om (ClojureScript обертка над ReactJs) и рендерил на сервере с помощью Nashorn, который появился в Java 8. Работало прекрасно.
                    • +4
                      Занятный у вас стек, сударь :)
                      • 0
                        Это я так, вне работы балуюсь.
                        А насчет стека — что так удивляет?
                        На кложе, не поверите, очень приятно писать: ФП, трансдьюсеры, иммутабельность, lazy-seqs, core.async, интеграция с JVM и возможность использования модулей на Java/Scala/..., core.typed там, вот это вот все… хотя последнее скорее костыль пока.
                        Очень уж лаконично и красиво получается все в целом.
                        Ах да, я еще и Datomic использую (http://www.datomic.com/), так что у меня еще и база на кложе. ;)
                        • +1
                          Да я в позитивном смысле, на самом-то деле. Clojure и edn мне тоже импонируют.
                      • 0
                        Насколько хороша производительность такого подхода (Nashorn + react)? Сравнима ли с JSP? А то я тут в новом проекте Thymeleaf заюзал и он 50 мс рендерит достаточно простую страницу. Не то, чтобы много, но жалко, 20 мс подготавливаем модель и потом 50 мс рендер. JSP скорее всего был бы раз в 10 быстрей. У меня до сервера пинг 3-5 мс, надеялся на ультрабыстрый отлик, а оно вон как получилось.

                        Кстати нет ли проблем с многопоточностью у такого подхода (Nashorn + react)? Рендерится ли в нескольких потоках или когда надо отрендерить ответ, все потоки будут вставать в очередь и по одному «дёргать» этот Nashorn?
                        • 0
                          Так JSP компилирется в стандартный Java класс (сервлет), конечно обработка будет быстрой.
                      • 0
                        Ничуть не смущают теги в JS, так или иначе генерация компонентов на JS к этому приводит, но на разных уровнях. Гораздо важнее один из пунктов статьи, про побочную сложность, дырявые абстракции и актуальность библиотеки через год-два. С другой стороны, волка бояться, так и продолжать писать на низком уровне, без абстракций и плюшек.
                        • 0
                          Упрямая архитектура (Opinionated Architecture)
                          Обычно — «жёсткая архитектура (фреймворка)».
                          • 0
                            С 5 пунктом я бы поспорил.
                            Дело в том, что реакт рендерится на сервере крайне неэффективно.

                            Оба метода, которые преобразуют компонент в строку (renderToString и renderToStaticMarkup) на каждую ноду вызывают свое преобразование. А вызов функции это более-менее затратная операция.

                            Я проверил на синтетическом тесте, в сравнении с *любимым шаблонизатором*
                            jsperf.com/react-rendertostring/7 — строится список из 100 элементов, у реакта производительность в десятки раз ниже.

                            Я пока в самом начале пути освоения реакта, но меня это настораживает. А еще больше настораживает что информации об этом не так много.
                            • –1
                              Если поисковики (для которых вы и будете рендерить на сервере страницу) заходят на ваш сайт чаще реальных пользователей (где все это будет на клиенте), то это значит что не библиотека неэффективна, а сам сайт ваш не эффективен :)
                              • –1
                                То есть реакт не подходит, если есть требование чтобы страницы для клиентов рендерились на сервере. В статье же написано что «Реакт можно использовать для подачи статичной разметки, без клиентского кода вообще.»
                                • 0
                                  Ну, я бы не стал его так использовать точно — это очень странно. В статье это написано в контексте доступа к контенту сайта поисковиками, т.е. можно делать нормальный динамический контент для людей и поддержать еще и отдачу контента поисковикам.

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

                            Самое читаемое