Нетрудно догадаться, что большинство разработчиков сейчас используют какие-либо фреймворки для разработки приложений. Они помогают нам структурировать сложные приложения и экономят время. Каждый день можно наблюдать большое количество обсуждений, какой фреймворк лучше, какой нужно учить, и тому подобное. Так что, в этот раз я поделюсь своим опытом и отвечу на вопрос: «Почему я перешел на Cycle.js с React?».
React, возможно, самый популярный frontend-фреймворк (на момент 2017) с огромным сообществом. Я большой фанат этого фреймворка и он мне помог изменить взгляд на веб-приложения и их разработку. Некоторые любят его, а кто-то считает, что он не так хорош.
Большинство использует React без мысли о том, что есть лучший инструмент или способ разработки веб-приложений. Это дало мне толчок попробовать Cycle.js, как новый реактивный фреймворк, который становится все более и более популярным изо дня в день.
И в этой статье я хочу объяснить:
- Что такое реактивное программирование
- Как работает Cycle.js
- И почему он, на мой взгляд, лучше React
Что такое реактивное программирование?
Реактивное программирование (RP, РП) — это работа с асинхронными потоками данных. Если вы создавали веб-приложение, вы, возможно, уже писали много реактивного кода. В качестве примера можно привести событие клика — это асинхронный поток данных. Мы можем следить за ним и создавать побочные эффекты (side effects). Идея реактивного программирования — дать возможность создавать потоки данных где угодно и работать с ними. Тогда мы можем создать некоторые абстракции для всех побочных эффектов (side effects), которые куда проще использовать, поддерживать и тестировать.
Вы возможно зададите вопрос: «А зачем мне нужен этот новый „реактивный“ подход в программировании?». Ответ прост: Реактивное программирование помогает унифицировать код и делает его более последовательным. Больше не нужно думать о том, как что-то должно работать и как правильно это реализовать. Просто пишем код определенным образом, не беспокоясь с какими данными мы работаем (клики мышкой, http-запросы, веб-сокеты). Всё это — потоки данных, и каждый поток имеет множество методов, которые позволяют работать с этими данным, например map
или filter
. Эти функции возвращают новые потоки, которые могут быть так же использованы.
Реактивное программирование предоставляет абстракции, а это дает возможность концентрироваться на бизнес-логике.
Реактивное программирование в JavaScript
В Javascript есть пара замечательных библиотек для работы с потоками данных. Одна из них — это всем известный Rx-JS, расширение ReactiveX, — API для асинхронного программирования с отслеживаемыми потоками данных. Вы можете создать Observable (поток данных) и управлять им множеством функций.
Вторая библиотека — это Most.js. Она имеет лучшую производительность, что подтверждается тестами.
Также стоит отметить одну небольшую и быструю библиотеку xstream, написанную автором Cycle.js. Она содержит 26 методов и весит 30kb. Это одна из самых быстрых библиотек для реактивного программирования на JS.
Как раз, в примерах к этой статье используется библиотека xstream. Cycle.js создана быть небольшим фреймворкам и я хочу использовать именно легковесную библиотеку в паре с Cycle.js.
Что такое Cycle.js?
Cycle.js — это функциональный и реактивный Javascript-фреймворк. Он предоставляет абстракции для приложений в виде чистой функции main()
. В функциональном программировании функции должны принимать на вход параметры и что-то возвращать без побочных эффектов. В Cycle.js функция main()
на вход принимает параметры из внешнего мира и пишет тоже во внешний мир. Побочные эффекты реализуются с помощью драйверов. Драйверы — это плагины, которые управляют DOM'ом, HTTP-запросами, вебсокетами и так далее.
Cycle.js помогает создавать пользовательский интерфейс, тестировать его и писать повторно используемый код. Каждый компонент представляет собой чистую функцию, которые могут запускаться независимо.
Основное API имеет только одну функцию, run()
с двумя аргументами:
run(app, drivers);
app
— основная чистая функция, а drivers
— это вышеуказанные драйверы.
Функционал Cycle.js разделен на несколько небольших модулей:
- @cycle/dom – коллекция драйверов для работы с DOM; Это DOM-драйвер и HTML-драйвер, основанный на snabdom virtual DOM библиотеке
- @cycle/history – драйвер History API
- @cycle/http – драйвер HTTP запросов, основанный на superagent
- @cycle/isolate – функция создания изолированных dataflow-компонентов
- @cycle/jsonp – JSONP-драйвер
- @cycle/most-run –
run
-функция для работы сmost
- @cycle/run –
run
-функция для работы сxstream
- @cycle/rxjs-run –
run
-функция для работы сrxjs
Cycle.js код
Хотите увидеть немного Cycle.js-кода? Мы создадим простое приложение, демонстрирующее как все это работает. Мне кажется, что старое доброе приложение-счетчик, будет идеальным для этого примера. Мы увидим, как работать с DOM-событиями и перерендериванием DOM-элементов.
Давайте создадим два файла: index.html
и main.js
. index.html
будет содержать только основной файл со скриптами, где и прописана вся логика.
package.json
, так что запустим
npm init -y
Затем установим основные зависимости
npm install @cycle/dom @cycle/run xstream --save
Это команда поставит @cycle/dom
, @cycle/xstream-run
, and xstream
. Также нужны babel
, browserify
и mkdirp
, установим их:
npm install babel-cli babel-preset-es2015 babel-register babelify browserify mkdirp --save-dev
Чтобы работать с Babel, создадим .babelrc
-файл в корне директории со следующим содержимым:
{
"presets": ["es2015"]
}
Также нам нужно добавить скрипты, которые упрощают запуск команд, в package.json
"scripts": {
"prebrowserify": "mkdirp dist",
"browserify": "browserify main.js -t babelify --outfile dist/main.js",
"start": "npm install && npm run browserify && echo 'OPEN index.html IN YOUR BROWSER'"
}
Для запуска нашего Cycle.js-приложения, нужно запустить команду
npm run start
Все. Установка закончена, теперь мы можем начать писать код.
Начнем с добавления небольшого HTML-кода внутри index.html
< !DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>Cycle.js counter</title>
</head>
<body>
<div id="main"></div>
<script src="./dist/main.js"></script>
</body>
</html>
Мы создали div
с идентификатором main
. Cycle.js свяжется с этим элементом и будет в него рендерить все приложение. Мы также подключили dist/main.js
-файл. Это проведенный через babel, транспайленный и объединенный js-файл, который будет создан из main.js
-файла.
Время написать немного Cycle.js-кода. Откроем main.js
и импортируем все необходимые зависимости:
import xs from 'xstream';
import { run } from '@cycle/run';
import { div, button, p, makeDOMDriver } from '@cycle/dom';
Мы включили xstream
, run
, makeDOMDriver
и функции, которые помогут нам работать с Virtual DOM (div
, button
и p
).
Напишем основную main
-функцию:
function main(sources) {
const action$ = xs.merge(
sources.DOM.select('.decrement').events('click').map(ev => -1),
sources.DOM.select('.increment').events('click').map(ev => +1)
);
const count$ = action$.fold((acc, x) => acc + x, 0);
const vdom$ = count$.map(count =>
div([
button('.decrement', 'Decrement'),
button('.increment', 'Increment'),
p('Counter: ' + count)
])
);
return {
DOM: vdom$,
};
}
run(main, {
DOM: makeDOMDriver('#main')
});
Это наша основная main
-функция. Она берет на вход параметры и возвращает результат. Входные параметры — это потоки DOM (DOM streams), а результат — это Virtual DOM. Давайте начнем объяснение шаг за шагом:
const action$ = xs.merge(
sources.DOM.select('.decrement').events('click').map(ev => -1),
sources.DOM.select('.increment').events('click').map(ev => +1)
);
Здесь мы объединяем два потока в один поток, называемый action$
(здесь используем соглашение о суффиксе "$" тех переменных, которые представлют собой поток данных). Один из потоков является кликом по кнопке (.decrement
) уменьшающей на единицу счетчик, второй — по другой, увеличивающей счетчик, кнопке (.increment
). Мы связываем эти события c числами -1
и +1
, соответственно. В конце слияния, поток action$
будет выглядеть следующим образом:
----(-1)-----(+1)------(-1)------(-1)------
Создадим еще один поток count$
const count$ = action$.fold((acc, x) => acc + x, 0);
Функция fold
прекрасно подходит для этой цели. Она принимает два аргумента: accumulate
и seed
. seed
is firstly emitted until the event comes. Следующее событие комбинируется с первым, основываясь на accumulate
-функции. Это практически функция-reduce()
для потоков.
Наш поток count$
получает 0 как начальное значение, затем при каждом новом значении из action$
-потока, мы суммируем с текущим значением count$
-потока.
В самом конце, завершая цикл работы функции, нужно запустить функцию run
под main
.
Последний шаг — создать Virtual DOM:
const vdom$ = count$.map(count =>
div([
button('.decrement', 'Decrement'),
button('.increment', 'Increment'),
p('Counter: ' + count)
])
);
Мы сопоставляем данные в потоке count$
и возвращаем Virtual DOM для каждого элемента в потоке. Virtual DOM содержит одну div
-обертку, две кнопки и параграф. Как можно заметить, Cycle.js работает с DOM с помощью JS-функций, но JSX также можно использовать.
В конце main
-функции нужно возвратить наш Virtual DOM:
return {
DOM: vdom$,
};
Мы передаем main
-функцию и DOM-драйвер, который подключен к и получаем поток событий для этого div
-элемента. Мы завершаем наш цикл и создаем прекрасное Cycle.js-приложение.
Вот как это работает:
Вот и все! Таким образом и можно работать с DOM-потоками. Если вы хотите посмотреть как работать с HTTP-потоками в Cycle.js, я написал статью[eng]об этом.
Весь код можно найти на GitHub-репозитории.
Почему я перешел с React на Cycle.js?
Сейчас вы поняли базовые принципы реактивного программирования и уже увидели простой пример на Cycle.js, так что давайте поговорим, почему я буду использовать эту связку для следующего моего проекта.
При разработке веб-приложений управление огромным количеством кода и данными из разных источников является большой проблемой. Я фанат React и у меня было множество проектов, но React не решал всех моих проблем.
React хорошо себя проявлял, когда дело доходило до рендеринга небольших данных и изменения состояния приложения. На самом деле, его методология компонентов потрясающа и реально помогает писать качественный, поддерживаемый и тестируемый код. Но все время чего-то не хватало.
Давайте рассмотрим плюсы и минусы использования Cycle.js вместо React.
Плюсы
1. Большая кодовая база
Когда приложение становится большим, при использовании React возникают проблемы. Представим, что у нас 100 компонентов внутри 100 контейнеров, и каждый из них имеет собственный функционал, тесты и стили. Это очень много строк кода внутри множества файлов внутри кучи директорий. В таком случае трудно переключаться между всеми этими файлами.
2. Потоки данных
Для меня наибольшая проблема React — это потоки данных. React изначально не разрабатывался для работы с потоками данных, и этого нет в ядре React. Разработчики пытались решить это, и у нас есть множество библиотек и методологий, которые решают эту проблему. Наиболее популярный — это Redux. Но он не совершенен. Вам нужно потратить много времени, чтобы сконфигурировать его и нужно написать много кода, что позволит просто работать с потоками данных.
С Cycle.js же, другая история, создатель хотел сделать фреймворк таким, чтобы он изначально работал с потоками данных, так что вам не нужно думать о этой реализации. Все что вам нужно — это писать функции, которые оперируют с данными и Cycle.js возьмет на себя все остальное.
3. Побочные эффекты функций (Side effects)
React имеет некоторые проблемы в работе с побочными эффектами (side effects). Нет стандартизированного подхода в работе с побочными эффектами (side effects) в React-приложениях. Есть множество инструментов, которые помогают работать, но это так же отнимает время на установку и изучения их. Наиболее популярные — это redux-saga, redux-effects, redux-side-effects, и redux-loop. Видите, что я имею ввиду? Их множество. И вам нужно выбирать, что учить и что внедрять в ваш проект.
4. Функциональное программирование
Создатели React утверждают, что React использует функциональный подход в программировании, но это не совсем правда. Там много ООП, классов, использования this
, что порождает головную боль, если что-то пойдет не так.
Cycle.js построен на функциональной парадигме. Все в нем — это функции, которые не зависят от внешнего состояния. Там даже нет классов. Это куда проще тестировать и поддерживать.
Минусы
1. Сообщество
В настоящее время React является наиболее популярным фреймворком и используется повсюду. Cycle.js — нет. Он все еще не очень популярный, и это будет проблемой, когда вы столкнетесь с проблемой, и найти решение в сети будет не таким легким. Временами решения проблемы нет, и вам придется решать ее самому.
Это не проблема, когда вы работаете над своим проектом и у вас много свободного времени. Но что будет, если вы работаете в компании и у вас дедлайн на носу? Вы потратите много времени на дебаггинге вашего же кода.
Но это меняется. Много разработчиков начинают использовать Cycke.js и говорить о нем, о проблемах. Решать их вместе. Cycle.js также имеет хорошую документацию со множеством примеров. У меня не было настолько сложных ошибок, которые было трудно дебажить.
2. Изучение нового подхода
Реактивное программирование отличается от обычного и вам нужно сперва потратить некоторое время, чтобы понять как работать. В результате будет легко, но если у вас скорый дедлайн, то изучение нового подхода будет проблемой.
3. Некоторые приложения не нуждаются в реактивном подходе
Да, некоторые приложения не должны быть «реактивными». Блоки, продающие сайты, лэндинги и многие другие статичные сайты с ограниченной функциональностью остро не нуждаются в реактивном подходе. Здесь нет данных, которые проходят через все приложение в реальном времени, нет стольких форм и кнопок. Используя реактивный фреймворк, возможно, вы сделаете такие сайты медленнее. Вы должны понимать, когда приложение нуждается в Cycle.js и реактивном подходе, а когда — нет.
Заключение
Идеальный фреймворк должен помогать фокусироваться на создании функционала, а не должен принуждать к написанию шаблонного кода. Мне кажется, что Cycle.js показывает, что это реально возможно и дает толчок к поиску лучших подходов к написанию кода и созданию функционала. Но нужно помнить, что нет ничего совершенного и всегда есть место для улучшений.
А вы пробовали реактивное программирование или Cycle.js? Убедил ли я вас попробовать? Дайте знать, что вы думаете в комментариях ставьте лайк, подписывайтесь на...!
Автор: Carduelis