Последние полгода много пишут о неоправданной сложности клиентского JavaScript. Недавняя статья How it feels to learn JavaScript in 2016 и ее перевод на хабре вызвали много внимания, критика во многом справедливая, но...
Усложнять просто, упрощать сложно. (Один из законов Мерфи)
В этой статье я дам практические советы, как можно просто сделать фронт-энд приложение, используя при этом современные технологии. Вначале практические детали реализации, а в конце статьи будет анализ выбранного стека.
Как пример используется приложение для работы с коллекцией фильмов. Фильмы отображаются в списке с постраничной выборкой, поиском, сортировкой, редактированием и удалением.
Использованный стек: create-react-app как сборщик для клиента, React, bootstrap, API с json-server или json-заглушки.
Работающее демо здесь: Movies List.
В уже упомянутой статье, ведется диалог между опытным фронт-энд разработчиком и бэк-энд разработчиком, который немного знает фронт-энд и хочет написать простое клиентское приложение используя современные технологии. Фронт-энд "гуру" вываливает все многообразие технологий которое можно использовать, и так озадачивает своего коллегу, что тот отказывается от своей идеи вообще или решает писать все по-старому с JQuery.
Я попытаюсь дать более практические советы о том как это сделать, более того покажу код готового приложения, чтобы можно было использовать его как пример.
GitHub репозиторий здесь.
Клиентская сборка с React Scripts
Первая проблема в современной фронт-энд разработке — это необходимость клиентских сборок. Мы будем использовать готовую build систему, где все сразу работает "из коробки".
Create-react-app или React Scripts — это молодой проект (первый комит в июне 2016) созданный Дэном Абрамовым, создателем Redux, который сейчас работает в facebook.
Чтобы начать
# установить create-react-app глобально
npm i -g create-react-app
# создать новое приложение в папке my-app
create-react-app my-app
React-scripts билдит клиентское приложение, без необходимости дополнительной настройки. Поддерживаются 2 режима development и production.
В режиме development используются клиентские сборки в памяти (с WebPack dev-server) сразу работает hot-reload (вы меняете код, страница перегружается), код проверяется линтером (например, если у вас есть неиспользуемая переменная, будет соответствующее предупреждение) и другое.
Для запуска в development режиме, просто:
#cd my-app
npm start
Если нужно сделать production сборку:
npm run build
Это создаст сборку приложения в папке "/build", где будет минимизированная и готовая к развертке версия.
Для импорта стилей и путей к картинкам, используется метод WebPack, просто делайте import для нужных ресурсов.
//импорт bootstrap стили из папки npm пакета
import '../node_modules/bootstrap/dist/css/bootstrap.css';
//импорт картинки из локальной папки приложения, в somePicture путь к картинке
import somePicture from '../media/picture.png';
Более подробно о работе с react-scripts читайте на GitHub страничке проекта.
React
В качестве JS фреймворка мы выбираем React. Есть другие хорошие опции (VueJS, Angular2), но в отличии от них, React имеет наиболее стабильную экосистему. Это означает стабильность API (не сильно меняется от версии к версии), поддержку в IDE, устоявшийся набор дополнительных библиотек (Redux, react-router), набор готовых компонентов (react-bootstrap, material-ui).
При этом, сам синтаксис React JSX не прост для начинающего и это, наверное, самое сложное с чем придется столкнуться в выбранном стеке.
Само приложение movies-list умышлено написано просто, чтобы было легче понятно для начинающих (например, в реальном приложении можно было бы сделать более детальное разбиение на компоненты).
Существует огромное количество всевозможных учебников по React, для выбранного стека вам нужно чтобы в учебнике использовался ES6 синтаксис (компоненты объявляются через class), и вам не нужно изучать Redux или React Router. С ними вы можете разобраться позже, если будет необходимость.
Примером такого учебника может быть React Fundamentals on Eggheads, стоит так же почитать официальную документацию.
Дополнительные модули
Модули используются как npm пакеты.
В качестве CSS фреймовка используется "bootstrap". Для интеграции с React используются компоненты "reeact-bootstrap" (хорошо описаны на сайте проекта).
Пакет "toastr" используется для всплывающих сообщений об ошибке или уведомлении об успешной операции (например: фильм был сохранен). Этот пакет требует включение "jquery" и для реального проекта может иметь смысл найти аналог на React, чтобы не включать JQuery и уменьшить размер упакованного приложения.
Для выбора нескольких значений для жанра фильмов используется "react-select" — продвинутая версия компонента для выпадающего списка.
При использовании ES6 классов нужно делать связывание (bind) для функций. Пакет "react-autobind" упрощает эту задачу, потенциально может повлиять на производительность (вы привязываете все методы, а не только те, где это нужно), но облегчает разработку. Подробнее о bind в React здесь.
//вместо:
constructor() {
super()
this.update = this.update.bind(this);
//... все другие методы
}
//c autobind
import autoBind from 'react-autobind';
...
constructor() {
super()
autoBind(this);
}
Для более удобной манипуляции css классами используется пакет "classnames".
import classnames from 'classnames';
let oneClass = classnames('foo', 'bar'); //значение "foo bar"
let isActive = true;
let anotherClass = classnames({
'foo': true,
'bar': false,
'active': isActive
}) // значение "foo active"
API
Если у вас нет возможности или желания использовать серверную часть для клиентского приложения, то есть несколько альтернативных возможностей:
JSON-заглушки — начальные данные приложения хранятся в JSON файле, который импортируется как внешний ресурс.
import jsonData from '../myJsonFile.json';
В дальнейшем все операции происходят уже с загруженными данными. Вы реализуете операции поиска, редактирования, удаления самостоятельно, работая с начальными данными из JSON.
В movie-list этот подход используется по умолчанию, логика в файле 'movieServiceStubs'.
Использование json-server — пакет json-server при запуске дает доступ к набору API на основе структуры JSON файла.
Например, если у вас есть db.json в котором есть массив movies, то автоматически будут доступны следующие API:
GET /movies
GET /movies/1
POST /movies
PUT /movies/1
PATCH /movies/1
DELETE /movies/1
При этом, API меняют исходный db.json файл, для GET запросов поддерживается поиск, постраничная выборка, сортировка.
Кроме этого json-server поддерживает много дополнительных опций — кастомные маршруты, авто-генерация тестовых данных, поддержка связей между разными элементами.
В movie-list есть возможность работы с json-server, нужно использовать movieService вместо movieServiceStub, кроме этого:
#установить json-server глобально
npm i -g json-server
##запустить json-server через npm-scripts
npm run server
Для AJAX запросов в React Scripts предлагается использовать fetch API. Это новый браузерный стандарт, которые более удобен, чем XMLHttpRequest. Для поддержки старых браузеров в React Scripts используется полифил.
В отличии от JQuery, axios и других клиентских библиотек, Fetch это стандарт, который можно использовать уже сейчас и который будет всерьез и надолго.
Языковые функции
Чтобы не усложнять код дополнительными библиотеками, в movie-list я использую только ES6 фичи (без lodash)
Помимо стрелочных функций ((x) => ...) стоит обратить внимание на такие, как:
Шаблонные строки
Итерация по колекции for… of ...
Клонирование объекта c Object.assign
Методы массивов map, filter, reduce
Благодаря новым функциям ES6, в Lodash и подобных библиотеках уже нет такой острой необходимости, хотя их использование все равно может быть полезно.
В проекте не используются ES2016+ фичи, они поддерживаются React Scripts, но API менее надежны и хуже поддерживаются (документация, примеры, поддержка в IDE и др.). Если вам все-таки интересно, можете попробовать использование async/await вместо Promise и static свойства в React компонентах.
Публикация
Есть много сайтов на которых вы можете выложить статические ресурсы (HTML/CSS/JS). React Scripts предлагает использовать для этого GitHub pages, в документации описано как это сделать и после 5-минутной настройки вы можете опубликовать демо-версию своего приложения. При этом, необходимо использовать json-заглушки, а это значит приложение потеряет свое состояние после полной перегрузки страницы, что для демо даже лучше.
Анализ выбранного стека
В уже упомянутой статье указываются несколько проблем современного фронт-энда:
Необходимость клиентских билдов
Во-первых, зачем они вообще нужны? Почему нельзя как раньше просто включать скрипт в HTML странички и просто начинать писать код.
В основном, из-за того, что некоторые браузеры очень медленно имплементируют новые стандарты (не будем показывать пальцем, но в основном это касается IE). Пока эти браузеры не уйдут в историю (а это как минимум пару лет), будет существовать необходимость в клиентских сборках, хотя бы для того, чтобы использовать новые функции языка ES6. Помимо этого, если у вас большое приложение, то вам стоит минифицировать ваш код, и если он разбит на много файлов объединять их в одну сборку с учетом связей (если модуль A использует модуль Б, то модуль Б должен быть включен перед модулем A).
Самостоятельно настраивать клиентский билд сложно. Нужно подключить webpack, babel, с десяток webpack loaders, поддерживать dev/prod сборки. На это может уйти куча времени и нервных переживаний, даже при использовании boilerplate вам будет не просто его выбрать, а потом поддерживать. К счастью, появились сборщики (пока не много) для клиентского приложения, в которых все это скрыто внутри отдельного пакета и все что вам нужно это подключить этот сборщик и писать свое приложение с учетом определенных особенностей.
Уже существуют аналоги React Scripts, для VueJS это vbuild, для Angular2 альтернативы я пока не видел, но есть билд система для Ionic2 который построен на Angular2. Эти проекты пока еще не так обкатаны, но появление подобных инструментов лишь вопрос времени.
Некоторые сомневаются, что это подходит для серьезных приложений. Это вполне возможно, но при использовании готовой системы клиентской сборки вы должны соблюдать некоторые соглашения, например, точка входа в одна и находится в src/index.js, не используются CSS препроцессоры, код проверяется определенным набором правил линтера. При этом, само приложение может быть как простым "hello world" так и сложным клиентом на много страниц. К примеру, в проекте Contoso Express react scripts используются для гораздо более сложного приложения.
В конце концов, если через несколько месяцев вам понадобится что-то чего нельзя добиться готовым сборщиком, вы можете сделать свое кастомное решение, но вам не придется заниматься этим в самом начале.
Проблема быстрого устаревания технологий
Вы учите новую технологию, тратите много времени, а она устаревает через полгода или в новой версии API кардинально меняются и приходиться разбираться заново.
Это справедливо для многих технологий, цена прогресса, но не для всех. Если не хочется столкнуться с подобной ситуацией, нужно просто правильно выбирать стек. В нашем стеке большинство технологий давно существуют, работают надежно и не будут сильно меняться в дальнейшем.
Нельзя с полной уверенностью говорить про React (хотя после перехода на ES6 синтаксис, для компонентов никаких значительных изменений в базовом синтаксисе не было). Но что касается ES6, Fetch, Promises — это то, что уже является стандартом JS и будет актуально многие годы, и уже поддерживается в большинстве браузеров.
Проблема слишком большого выбора
Умение правильно подбирать инструменты важно не только в программировании. Проблема в JS, что нет основного (mainstream) подхода, для большинства случаев, как например в .NET. И выбор сильно усложняется. В этом плане экосистема React достаточно устоялась, например, если вначале для React существовало множество Flux имплементаций, то сейчас большинство приложений использует Redux, есть только одна популярная версия для клиентской маршрутизации React Router, и т.д.
При этом важно не использовать лишние инструменты, используя прогрессивный процесс разработки. Начать с необходимого минимума, добавляя новые компоненты только при реальной необходимости. Например, в уже не раз упомянутой статье, для простого одностраничного проложения, предлагались некоторые технологии, в которых совсем не было смысла:
- ES2016+ — приятное дополнение, но особой необходимости нет.
- TypeScript/Flow — дает преимущества строгой типизации, но требует дополнительных усилий при настройке и использовании, для начинающих будет лишь дополнительной морокой (в React Scripts можно подключать Flow).
- Functional programming — для простого приложения, необходимости в этом нет вообще, а для сложного приложения без этого вполне можно обойтись.
- Lodash/Rumbda — с использованием ES6 многие полезные функции стали доступны на уровне языка, можно начать без них.
- Flux(Redux) — для сложного приложения необходим способ совместного использования состояния (данных) приложения для различных компонентов, для простого приложения отказ от них облегчит начальную разработку.
- Тестирование UI — для простого приложения без них можно обойтись (в React Scripts есть поддержка Jest для тестирования React компонентов).
Спасибо, тем кто дочитал до конца.
Happy coding! Stay tuned.
Автор: Yeggor