Путь длиною в React

Автор оригинала: Emmanuel Luciano
  • Перевод
В 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 не так уж плох.
Voximplant
156,20
Облачная платформа голосовой и видеотелефонии
Поделиться публикацией

Комментарии 31

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

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

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

        0
        Гифок было бы достаточно с заголовками )
      0
      Окей, тема про TS и React :) А давайте обсудим такой момент.

      Как вы делаете dependency injection в React-компоненты? Или как обходитесь без него, не создавая сильных связей? Передаете по цепочке с корня? Контексты? Какие-то сторонние библиотеки?
        +2
        dependency injection
        import Blabla from 'blabla'; // это не сложно
        

        Котекст = redux

        Компоненты есть тупые и есть умные (работают с контекстом напрямую, сами ничего не рисуют а только передают данные в тупые компоненты), т.о. цепочки компонентов очень короткие
          0
          Вот «import Blabla from 'blabla'» и хочется избежать — за исключением того случая, когда blabla — это blabla.ts.d с интерфейсом. Dependency inversion principle, вот это всё.
            0
            Каждому свое, меня не напрягает.
              0
              Это нормально :) Меня не то что напрягает (хотя есть немного), но больше для конкретной практической цели нужна отвязка от реализации.

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

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

                    import Inject1 from './inject1';


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

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

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

                        Сможете объяснить разницу в применении?
                        constructor(inject1: Inject1){
                        }
                        и
                        import Inject1 from './inject1';
                        constructor(){
                        this.inject1 = Inject1();
                        }
                          0
                          допустим у нас есть модуль inject2, который содержит Inject2 и его интерфейс совместим с Inject1
                          если мы захотим иметь несколько инстансов компонента с разными inject'ами
                          первый вариант этому не воспрепятствует
                          в отличии от второго

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

                            К слову, в nodejs и typescript, в частности, нет некоторых возможностей, например, деструктора, в связи с чем нельзя вручную удалить созданную зависимость, приходится играться с GC. А также, нет рефлексии, это частично решается декораторами.
                              0
                              Но ведь тогда все должны ссылаться явно на модуль, инициализирующий все это добро (собственно, контейнер), чего хотелось бы избежать и получать все инстансы неявно.
                              Кажется, что достичь этого можно малой кровью, декорируя поля на классе либо через мета-данные TS, либо через символы, импортируемые из модулей, содержащих нужные классы.
                              В принципе все это достаточно просто делается через react-контекст, создаваемый компонентом-провайдером конфигурации контейнера. А компоненты, которым нужны инстансы, декорируются с простановкой contextTypes и т.п. Там же можно вклиниться в componentWillUnmount и поубивать все инстансы.
                              С другой стороны, возникает закономерный вопрос, на кой это все, если redux-экшенов для ui-слоя должно хватить с лихвой? Экшены, конечно, уже могут инжектить инстансы каких-либо сервисов.
                                0
                                Нет, мы ссылаемся не на контейнер и не на конкретный instance, а просто с помощью import — объявляем мета-данные модуля, точно так же, как и любым другим способом (которые вы перечислили относительно класса). Почему вы хотите этого избежать? DI нужен для unit-тестирования и композиции, этот способ прекрасно подходит для этого (я описал выше).
                                А для React-компонентов DI бывает нужен для внешних UI-библиотек, ну и, видимо, для противников чистых функций.
                                  0
                                  Вот теперь сижу и думаю, а ведь действительно, почему? Видимо, дело привычки отделять дизайнтайм от рантайма. Этакое «ожидание DOMContentLoaded». Да и нежелание мокать require в ноде — видимо, тоже.
                                  Вы выше написали, что, при разработке DI с помощью разных подходов, импорты в итоге победели. Можете объяснить почему? Какие значимые плюсы по сравнению с классическим подходом?

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

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

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

                                      Со способом angular1 все нормально (настолько, насколько нормально можно сделать в рамках ES5), просто его часто неправильно используют. Надо писать не module.service('Foo', пошла_пачка_кода...), а module.service('Foo', SomeFooImplementation), и рассматривать определение модуля как конфигурацию контейнера.
                                0
                                ок, а условие «ХОЧУ1» откуда берётся?
                0
              0
              По цепочке, постепенно разыменовывая при каждой возможности.
                0
                Мы для этих целей сделали свой микрофреймворк — intakejs.
                В ближайшее время готовим к нему нормальную документацию, а потом и статью напишем.
                +2
                Каждая статья сравнение заканчивается тем, что «чем пользоваться выбирайте сами, всё хорошо»
                  +3
                  Не смог прочитать пост из-за постоянного мельтешения.
                    0
                    Поясните, пожалуйста, что не так с документацией по Ангуляру? Есть пошаговый гайд, есть описание для разработки директив/сервисов и есть описание API. Мне в свое время этого хватило с головой для старта.
                      0
                      Признаюсь, вначале пути от «вёрстки до JS» я тоже пошел в документацию к Angular и не смог разобраться достаточно, чтобы решить поставленную в тот момент задачу. Но уже через пол года «иероглифы» превратились в понятные буквы и документация показать вполне вменяемой. На мой взгляд это вопрос уровня понимания и качества восприятия.
                        0
                        Например, документация по Директивам — пример ОЧЕНЬ плохой документации для неподготовленного.
                        +1
                        Кто-нибудь на данный момент использует React без Flux и есть ли стоящие альтернативы как таковые?

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

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

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