В VoxImplant мы используем React.js и TypeScript для всех новых фронтенд-проектов. Но стараемся не зацикливаться на выбранных инструментах и внимательно смотрим по сторонам – не летит ли орел, не ползет ли змея, не случилось ли что интересное у других фреймворков. Недавно нам попалась статья, автор которой подробно и вдумчиво сравнивает React с Ember. И, да, у него большой опыт работы и с первым, и со вторым (а не как это обычно бывает). Предлагаем вашему вниманию адаптированный, и, надеемся, легко читаемый, перевод.

3 января 2016 года Дэн Абрамов опубликовал твит:

«Хочу, чтобы в 2016 году больше программистов React создали что-нибудь с помощью Angular, Ember, Cycle. А программисты Angular, Ember, Cycle – на React. Вместе мы узнаем больше».

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

Это было как-то так:


В борьбе с документацией Angular

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

После неудачного опыта я стал искать другие варианты и решил попробовать Ember.


Моя реакция на документацию Ember

Изучение Ember имеет такие же трудности, как изучение чего-либо, вообще. Но по сравнению с Angular, учиться было легче. Не утверждаю, что Ember легче Angular, а Angular тяжелее, чем Ember. Но подчеркну то, что я действительно люблю в сообществе Ember: внимание к документации. То, как сообщество ведет документацию, делает знакомство с Ember простым. С моей точки зрения, изучение Ember означает изучение соглашений, принятых в этом фреймворке. Ember применяет философию convention over configuration.

Фреймворк предоставляет разработчику:
  • раутинг с помощью Ember Router,
  • «выполнялку» для тестов,
  • фреймворк для тестов,
  • библиотеку data persistence с ember-data,
  • библиотеку работы с асинхронным кодом в RSVP,
  • интерфейс командной строки в ember-cli,
  • стандартную структуру проекта в Pods
  • богатую систему аддонов.

Как вы могли заметить, в коробке инструментов Ember много дополнительных «плюшек». Потребуется время, чтобы понять, как это всё работает. Но после того, как осилили эту науку, вы сразу же становитесь очень продуктивным. Вы легко начинаете проект или присоединяетесь к нему, так как знаете, что где лежит.

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


Разработка с помощью React

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

После того, как вы настроите всё сами, обучитесь в процессе, что и где использовать – это будет ценным уроком для вас как разработчика. Полезно изучить, как использовать Webpack и лоадеры, как настроить дев сервер или выполнялку тестов, например Karma, с фреймворком тестирования, например, Mocha. Уверен, что знания, которые я получил, могут мне помочь в будущем, и я буду ценить всю работу, которая делается в Ember.

Самостоятельная настройка всего может выглядеть как-то так:



Но это того стоит!

Довольно просто изучить, как работать с React. В последние годы React повлиял на многие JavaScript-фреймворки. Ember не стал исключением. Он перенял подход «компоненты прежде всего», очень простой лайфцикл, систему «действия вверх, данные вниз», и другое.

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

После того, как я изучил и использовал Ember с React, не могу утверждать, что один фреймворк лучше другого. Оба представляют разные подходы к решению одних и тех же проблем. И к тому же представляют разную философию создания программного обеспечения. Думаю, что вместо того, чтобы выбирать, на какую сторону встать (как если бы это была война), мы должны отдать должное тяжелой работе, которая лежит в основе создания любого программного обеспечения с открытым программным кодом – которыми мы пользуемся, с чьей помощью обучаемся и, если можем, контрибьютим.

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

P.S. JSX не так уж плох.
Поделиться с друзьями
-->

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


  1. RubaXa
    19.07.2016 12:31
    +11

    Так и не понял, что хотел сказать автор оригинала. Зачем он переходил с Ember на React? Если у первого всё так замечательно, развитая экосистема и работа из коробки?..

    Похоже весь пост создан ради «прикольный джифок», а между ними вода.


    1. keksmen
      19.07.2016 13:49

      Поддержу тему анимации. "Гифки" доставили больше, чем сама статья:D


      1. rockyou
        19.07.2016 13:56

        Гифок было бы достаточно с заголовками )


  1. symbix
    19.07.2016 14:49

    Окей, тема про TS и React :) А давайте обсудим такой момент.

    Как вы делаете dependency injection в React-компоненты? Или как обходитесь без него, не создавая сильных связей? Передаете по цепочке с корня? Контексты? Какие-то сторонние библиотеки?


    1. gibson_dev
      19.07.2016 14:56
      +2

      dependency injection

      import Blabla from 'blabla'; // это не сложно
      

      Котекст = redux

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


      1. symbix
        19.07.2016 15:02

        Вот «import Blabla from 'blabla'» и хочется избежать — за исключением того случая, когда blabla — это blabla.ts.d с интерфейсом. Dependency inversion principle, вот это всё.


        1. gibson_dev
          19.07.2016 15:03

          Каждому свое, меня не напрягает.


          1. symbix
            19.07.2016 15:07

            Это нормально :) Меня не то что напрягает (хотя есть немного), но больше для конкретной практической цели нужна отвязка от реализации.

            Будем считать, что вопрос адресован тем, кого напрягает. :)


            1. arvitaly
              19.07.2016 17:00

              Это и есть отвязка от реализации, просто вместо DI в конструкторе, через модули и явно. А в импортируемом модуле может быть уже что угодно — фабрика, синглтон и т.д.
              А если вас волнуют тесты, то «require» в nodejs mock-ается, а в https://facebook.github.io/jest/ из коробки.

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


              1. symbix
                19.07.2016 18:43

                Да понятно, просто с require получается аналог не DI, а сервис-локатора. А хочется именно DI.


                1. arvitaly
                  19.07.2016 19:20

                  Ну, начнем с того, что React вообще по идеологии закрыт от изменения компонента таким путем, в аргументах конструктора у него props, а свойства нельзя назначать снаружи. А все инициализации должны происходить в соответствующем этапе lifecycle. Например, в componentWillMount. Следовательно, получение там экземпляра нужного объекта хоть и не является классическим решением DI, но, принципиально ничем не отличается. Мы можем, через модули, все так же запрашивать именно этот класс, а не получать ссылку на ServiceLocator.
                  //component1.js

                  import Inject1 from './inject1';


                  componentWillMount(){
                  this.inject1 = Inject1();
                  }

                  //inject1.js
                  export default ()=>{
                  return new Inject1(params);
                  }

                  По-моему, это вопрос синтаксиса, а не паттернов.


                  1. satahippy
                    19.07.2016 20:56

                    DI — push подход
                    Service Locator — pull подход
                    У вас компонент сам тянет свои зависимости = Service Locator


                    1. arvitaly
                      19.07.2016 22:25

                      Service Locator — вполне конкретный паттерн, характеризующийся созданием одного контейнера для всех объектов и содержащий ссылки на все инъекции. В моем примере такого контейнера нет.
                      Просто, в случае Java и C# — внедрение зависимости происходит только через класс, а в случае nodejs — через модуль + класс.

                      Сможете объяснить разницу в применении?
                      constructor(inject1: Inject1){
                      }
                      и
                      import Inject1 from './inject1';
                      constructor(){
                      this.inject1 = Inject1();
                      }


                      1. satahippy
                        19.07.2016 22:39

                        допустим у нас есть модуль inject2, который содержит Inject2 и его интерфейс совместим с Inject1
                        если мы захотим иметь несколько инстансов компонента с разными inject'ами
                        первый вариант этому не воспрепятствует
                        в отличии от второго

                        может вы расскажите, как такие ситуации разруливаются в мире nodejs?


                        1. arvitaly
                          19.07.2016 23:19
                          +1

                          Точно так же, как в любом другом мире
                          //inject1.js — модуль, являющийся частью DIC, а не классом Inject1!!!
                          export default ()=>{
                          if (ХОЧУ1){
                          return new Inject1(params);
                          }else{
                          return new Inject2(params);
                          }
                          }
                          Повторюсь, я не изобрел велосипед, это всего лишь вопрос синтаксиса.

                          К слову, в nodejs и typescript, в частности, нет некоторых возможностей, например, деструктора, в связи с чем нельзя вручную удалить созданную зависимость, приходится играться с GC. А также, нет рефлексии, это частично решается декораторами.


                          1. raveclassic
                            20.07.2016 00:40

                            Но ведь тогда все должны ссылаться явно на модуль, инициализирующий все это добро (собственно, контейнер), чего хотелось бы избежать и получать все инстансы неявно.
                            Кажется, что достичь этого можно малой кровью, декорируя поля на классе либо через мета-данные TS, либо через символы, импортируемые из модулей, содержащих нужные классы.
                            В принципе все это достаточно просто делается через react-контекст, создаваемый компонентом-провайдером конфигурации контейнера. А компоненты, которым нужны инстансы, декорируются с простановкой contextTypes и т.п. Там же можно вклиниться в componentWillUnmount и поубивать все инстансы.
                            С другой стороны, возникает закономерный вопрос, на кой это все, если redux-экшенов для ui-слоя должно хватить с лихвой? Экшены, конечно, уже могут инжектить инстансы каких-либо сервисов.


                            1. arvitaly
                              20.07.2016 00:54

                              Нет, мы ссылаемся не на контейнер и не на конкретный instance, а просто с помощью import — объявляем мета-данные модуля, точно так же, как и любым другим способом (которые вы перечислили относительно класса). Почему вы хотите этого избежать? DI нужен для unit-тестирования и композиции, этот способ прекрасно подходит для этого (я описал выше).
                              А для React-компонентов DI бывает нужен для внешних UI-библиотек, ну и, видимо, для противников чистых функций.


                              1. raveclassic
                                20.07.2016 09:19

                                Вот теперь сижу и думаю, а ведь действительно, почему? Видимо, дело привычки отделять дизайнтайм от рантайма. Этакое «ожидание DOMContentLoaded». Да и нежелание мокать require в ноде — видимо, тоже.
                                Вы выше написали, что, при разработке DI с помощью разных подходов, импорты в итоге победели. Можете объяснить почему? Какие значимые плюсы по сравнению с классическим подходом?

                                Про UI-либы для реакта не понял, там же просто классы лежат, зачем их вставлять через DI? Тут-то как раз импорты подходят.


                                1. arvitaly
                                  20.07.2016 11:55

                                  Да, дело именно в разных подходах к файлам и импорту.
                                  Мокать тоже не обязательно, есть и другие способы, в DI-контейнере вставлять условие NODE_ENV, например. Для модуля опять же подмена произойдет неявно.

                                  Плюсы исходят из «недостатков-фич» nodejs, я описал их, в C# мы можем внедрять зависимость по типу, в JS такой возможности вообще нет, поэтому классический подход невозможен. Способ же Angular 1 — для меня является худшим примером, каждый контроллер знает именно об этом сервисе, а не об интерфейсе. Мы не можем подменить сервис в одном контроллере на другой, мы не можем переименовать сервис, все сильно связано со всем.
                                  Если же говорить про TypeScript, то, во-первых, способ через декораторы означает повторное объявление типов, рефлексия в зачаточном состоянии.
                                  Да и вопрос, должны ли классы, а не модули в NodeJS быть unit-ами открыт, несмотря на давление со стороны Java-сообщества (Google, в частности).

                                  По поводу, UI-либ, ну по той же причине, что и любой DI — неявное создание экземпляров или синглтоны. Допустим, есть некий сложный компонент на JQuery UI, требующий сложной инициализации, но который нужно вставлять в разные React-компоненты.


                                  1. symbix
                                    20.07.2016 16:41

                                    В свежем typescript достаточно декоратора с произвольным именем (скажем, Inject) — уже будут сгенерированны метаданные с необходимой информацией. В aurelia так сделано, например. А в angular2 обычно используется побочный эффект — декоратор Component уже есть.

                                    Со способом angular1 все нормально (настолько, насколько нормально можно сделать в рамках ES5), просто его часто неправильно используют. Надо писать не module.service('Foo', пошла_пачка_кода...), а module.service('Foo', SomeFooImplementation), и рассматривать определение модуля как конфигурацию контейнера.


                          1. satahippy
                            20.07.2016 09:37

                            ок, а условие «ХОЧУ1» откуда берётся?


        1. Aetet
          19.07.2016 23:31

    1. VolCh
      19.07.2016 19:08

      По цепочке, постепенно разыменовывая при каждой возможности.


    1. koroandr
      20.07.2016 13:31

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


  1. Quber
    19.07.2016 14:53
    +2

    Каждая статья сравнение заканчивается тем, что «чем пользоваться выбирайте сами, всё хорошо»


  1. nuklea
    19.07.2016 20:57
    +3

    Не смог прочитать пост из-за постоянного мельтешения.


  1. DigitalSmile
    20.07.2016 17:53

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


    1. psykeonfarm
      21.07.2016 22:11

      Признаюсь, вначале пути от «вёрстки до JS» я тоже пошел в документацию к Angular и не смог разобраться достаточно, чтобы решить поставленную в тот момент задачу. Но уже через пол года «иероглифы» превратились в понятные буквы и документация показать вполне вменяемой. На мой взгляд это вопрос уровня понимания и качества восприятия.


  1. psykeonfarm
    20.07.2016 18:51
    +1

    Кто-нибудь на данный момент использует React без Flux и есть ли стоящие альтернативы как таковые?

    А по теме поста, увы, нечего прокомментировать, но стало понятно, что по мнению автора документация Angular 2 сложнее документации Ember, у которого в коробке инструментов есть много дополнительных «плюшек». А React – это библиотека, не фреймворк и JSX не так уж плох. Спасибо!


    1. raveclassic
      22.07.2016 00:32

      Сама суть реакта подразумевает парадигму flux — данные вниз, действия вверх (или «в бок» для экшенов). Сам не пробовал, но вроде MobX набирает обороты.