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)
symbix
19.07.2016 14:49Окей, тема про TS и React :) А давайте обсудим такой момент.
Как вы делаете dependency injection в React-компоненты? Или как обходитесь без него, не создавая сильных связей? Передаете по цепочке с корня? Контексты? Какие-то сторонние библиотеки?gibson_dev
19.07.2016 14:56+2dependency injection
import Blabla from 'blabla'; // это не сложно
Котекст = redux
Компоненты есть тупые и есть умные (работают с контекстом напрямую, сами ничего не рисуют а только передают данные в тупые компоненты), т.о. цепочки компонентов очень короткиеsymbix
19.07.2016 15:02Вот «import Blabla from 'blabla'» и хочется избежать — за исключением того случая, когда blabla — это blabla.ts.d с интерфейсом. Dependency inversion principle, вот это всё.
gibson_dev
19.07.2016 15:03Каждому свое, меня не напрягает.
symbix
19.07.2016 15:07Это нормально :) Меня не то что напрягает (хотя есть немного), но больше для конкретной практической цели нужна отвязка от реализации.
Будем считать, что вопрос адресован тем, кого напрягает. :)arvitaly
19.07.2016 17:00Это и есть отвязка от реализации, просто вместо DI в конструкторе, через модули и явно. А в импортируемом модуле может быть уже что угодно — фабрика, синглтон и т.д.
А если вас волнуют тесты, то «require» в nodejs mock-ается, а в https://facebook.github.io/jest/ из коробки.
Но я когда-то делал DI для React и через свойства класса и через конструктор, очевидно, что для этого пришлось подменять React.createElement. И это тоже нормально, но, в итоге, импорты победили.symbix
19.07.2016 18:43Да понятно, просто с require получается аналог не DI, а сервис-локатора. А хочется именно DI.
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);
}
По-моему, это вопрос синтаксиса, а не паттернов.satahippy
19.07.2016 20:56DI — push подход
Service Locator — pull подход
У вас компонент сам тянет свои зависимости = Service Locatorarvitaly
19.07.2016 22:25Service Locator — вполне конкретный паттерн, характеризующийся созданием одного контейнера для всех объектов и содержащий ссылки на все инъекции. В моем примере такого контейнера нет.
Просто, в случае Java и C# — внедрение зависимости происходит только через класс, а в случае nodejs — через модуль + класс.
Сможете объяснить разницу в применении?
constructor(inject1: Inject1){
}
и
import Inject1 from './inject1';
constructor(){
this.inject1 = Inject1();
}satahippy
19.07.2016 22:39допустим у нас есть модуль inject2, который содержит Inject2 и его интерфейс совместим с Inject1
если мы захотим иметь несколько инстансов компонента с разными inject'ами
первый вариант этому не воспрепятствует
в отличии от второго
может вы расскажите, как такие ситуации разруливаются в мире nodejs?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. А также, нет рефлексии, это частично решается декораторами.raveclassic
20.07.2016 00:40Но ведь тогда все должны ссылаться явно на модуль, инициализирующий все это добро (собственно, контейнер), чего хотелось бы избежать и получать все инстансы неявно.
Кажется, что достичь этого можно малой кровью, декорируя поля на классе либо через мета-данные TS, либо через символы, импортируемые из модулей, содержащих нужные классы.
В принципе все это достаточно просто делается через react-контекст, создаваемый компонентом-провайдером конфигурации контейнера. А компоненты, которым нужны инстансы, декорируются с простановкой contextTypes и т.п. Там же можно вклиниться в componentWillUnmount и поубивать все инстансы.
С другой стороны, возникает закономерный вопрос, на кой это все, если redux-экшенов для ui-слоя должно хватить с лихвой? Экшены, конечно, уже могут инжектить инстансы каких-либо сервисов.arvitaly
20.07.2016 00:54Нет, мы ссылаемся не на контейнер и не на конкретный instance, а просто с помощью import — объявляем мета-данные модуля, точно так же, как и любым другим способом (которые вы перечислили относительно класса). Почему вы хотите этого избежать? DI нужен для unit-тестирования и композиции, этот способ прекрасно подходит для этого (я описал выше).
А для React-компонентов DI бывает нужен для внешних UI-библиотек, ну и, видимо, для противников чистых функций.raveclassic
20.07.2016 09:19Вот теперь сижу и думаю, а ведь действительно, почему? Видимо, дело привычки отделять дизайнтайм от рантайма. Этакое «ожидание DOMContentLoaded». Да и нежелание мокать require в ноде — видимо, тоже.
Вы выше написали, что, при разработке DI с помощью разных подходов, импорты в итоге победели. Можете объяснить почему? Какие значимые плюсы по сравнению с классическим подходом?
Про UI-либы для реакта не понял, там же просто классы лежат, зачем их вставлять через DI? Тут-то как раз импорты подходят.arvitaly
20.07.2016 11:55Да, дело именно в разных подходах к файлам и импорту.
Мокать тоже не обязательно, есть и другие способы, в DI-контейнере вставлять условие NODE_ENV, например. Для модуля опять же подмена произойдет неявно.
Плюсы исходят из «недостатков-фич» nodejs, я описал их, в C# мы можем внедрять зависимость по типу, в JS такой возможности вообще нет, поэтому классический подход невозможен. Способ же Angular 1 — для меня является худшим примером, каждый контроллер знает именно об этом сервисе, а не об интерфейсе. Мы не можем подменить сервис в одном контроллере на другой, мы не можем переименовать сервис, все сильно связано со всем.
Если же говорить про TypeScript, то, во-первых, способ через декораторы означает повторное объявление типов, рефлексия в зачаточном состоянии.
Да и вопрос, должны ли классы, а не модули в NodeJS быть unit-ами открыт, несмотря на давление со стороны Java-сообщества (Google, в частности).
По поводу, UI-либ, ну по той же причине, что и любой DI — неявное создание экземпляров или синглтоны. Допустим, есть некий сложный компонент на JQuery UI, требующий сложной инициализации, но который нужно вставлять в разные React-компоненты.symbix
20.07.2016 16:41В свежем typescript достаточно декоратора с произвольным именем (скажем, Inject) — уже будут сгенерированны метаданные с необходимой информацией. В aurelia так сделано, например. А в angular2 обычно используется побочный эффект — декоратор Component уже есть.
Со способом angular1 все нормально (настолько, насколько нормально можно сделать в рамках ES5), просто его часто неправильно используют. Надо писать не module.service('Foo', пошла_пачка_кода...), а module.service('Foo', SomeFooImplementation), и рассматривать определение модуля как конфигурацию контейнера.
Quber
19.07.2016 14:53+2Каждая статья сравнение заканчивается тем, что «чем пользоваться выбирайте сами, всё хорошо»
DigitalSmile
20.07.2016 17:53Поясните, пожалуйста, что не так с документацией по Ангуляру? Есть пошаговый гайд, есть описание для разработки директив/сервисов и есть описание API. Мне в свое время этого хватило с головой для старта.
psykeonfarm
21.07.2016 22:11Признаюсь, вначале пути от «вёрстки до JS» я тоже пошел в документацию к Angular и не смог разобраться достаточно, чтобы решить поставленную в тот момент задачу. Но уже через пол года «иероглифы» превратились в понятные буквы и документация показать вполне вменяемой. На мой взгляд это вопрос уровня понимания и качества восприятия.
psykeonfarm
20.07.2016 18:51+1Кто-нибудь на данный момент использует React без Flux и есть ли стоящие альтернативы как таковые?
А по теме поста, увы, нечего прокомментировать, но стало понятно, что по мнению автора документация Angular 2 сложнее документации Ember, у которого в коробке инструментов есть много дополнительных «плюшек». А React – это библиотека, не фреймворк и JSX не так уж плох. Спасибо!raveclassic
22.07.2016 00:32Сама суть реакта подразумевает парадигму flux — данные вниз, действия вверх (или «в бок» для экшенов). Сам не пробовал, но вроде MobX набирает обороты.
RubaXa
Так и не понял, что хотел сказать автор оригинала. Зачем он переходил с Ember на React? Если у первого всё так замечательно, развитая экосистема и работа из коробки?..
Похоже весь пост создан ради «прикольный джифок», а между ними вода.
keksmen
Поддержу тему анимации. "Гифки" доставили больше, чем сама статья:D
rockyou
Гифок было бы достаточно с заголовками )