Почему я перешел с React на Cycle.js
- вторник, 11 июля 2017 г. в 03:12:39
Нетрудно догадаться, что большинство разработчиков сейчас используют какие-либо фреймворки для разработки приложений. Они помогают нам структурировать сложные приложения и экономят время. Каждый день можно наблюдать большое количество обсуждений, какой фреймворк лучше, какой нужно учить, и тому подобное. Так что, в этот раз я поделюсь своим опытом и отвечу на вопрос: «Почему я перешел на Cycle.js с React?».
React, возможно, самый популярный frontend-фреймворк (на момент 2017) с огромным сообществом. Я большой фанат этого фреймворка и он мне помог изменить взгляд на веб-приложения и их разработку. Некоторые любят его, а кто-то считает, что он не так хорош.
Большинство использует React без мысли о том, что есть лучший инструмент или способ разработки веб-приложений. Это дало мне толчок попробовать Cycle.js, как новый реактивный фреймворк, который становится все более и более популярным изо дня в день.
map
или filter
. Эти функции возвращают новые потоки, которые могут быть так же использованы.Реактивное программирование предоставляет абстракции, а это дает возможность концентрироваться на бизнес-логике.
В Javascript есть пара замечательных библиотек для работы с потоками данных. Одна из них — это всем известный Rx-JS, расширение ReactiveX, — API для асинхронного программирования с отслеживаемыми потоками данных. Вы можете создать Observable (поток данных) и управлять им множеством функций.
Вторая библиотека — это Most.js. Она имеет лучшую производительность, что подтверждается тестами.
Также стоит отметить одну небольшую и быструю библиотеку xstream, написанную автором Cycle.js. Она содержит 26 методов и весит 30kb. Это одна из самых быстрых библиотек для реактивного программирования на JS.
Как раз, в примерах к этой статье используется библиотека xstream. Cycle.js создана быть небольшим фреймворкам и я хочу использовать именно легковесную библиотеку в паре с Cycle.js.
Cycle.js — это функциональный и реактивный Javascript-фреймворк. Он предоставляет абстракции для приложений в виде чистой функции main()
. В функциональном программировании функции должны принимать на вход параметры и что-то возвращать без побочных эффектов. В Cycle.js функция main()
на вход принимает параметры из внешнего мира и пишет тоже во внешний мир. Побочные эффекты реализуются с помощью драйверов. Драйверы — это плагины, которые управляют DOM'ом, HTTP-запросами, вебсокетами и так далее.
run()
с двумя аргументами:run(app, drivers);
app
— основная чистая функция, а drivers
— это вышеуказанные драйверы.run
-функция для работы с most
run
-функция для работы с xstream
run
-функция для работы с rxjs
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
.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'"
}
npm run start
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
-файла.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
.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-репозитории.
Сейчас вы поняли базовые принципы реактивного программирования и уже увидели простой пример на Cycle.js, так что давайте поговорим, почему я буду использовать эту связку для следующего моего проекта.
При разработке веб-приложений управление огромным количеством кода и данными из разных источников является большой проблемой. Я фанат React и у меня было множество проектов, но React не решал всех моих проблем.
React хорошо себя проявлял, когда дело доходило до рендеринга небольших данных и изменения состояния приложения. На самом деле, его методология компонентов потрясающа и реально помогает писать качественный, поддерживаемый и тестируемый код. Но все время чего-то не хватало.
Давайте рассмотрим плюсы и минусы использования Cycle.js вместо React.
Когда приложение становится большим, при использовании React возникают проблемы. Представим, что у нас 100 компонентов внутри 100 контейнеров, и каждый из них имеет собственный функционал, тесты и стили. Это очень много строк кода внутри множества файлов внутри кучи директорий. В таком случае трудно переключаться между всеми этими файлами.
Для меня наибольшая проблема React — это потоки данных. React изначально не разрабатывался для работы с потоками данных, и этого нет в ядре React. Разработчики пытались решить это, и у нас есть множество библиотек и методологий, которые решают эту проблему. Наиболее популярный — это Redux. Но он не совершенен. Вам нужно потратить много времени, чтобы сконфигурировать его и нужно написать много кода, что позволит просто работать с потоками данных.
this
, что порождает головную боль, если что-то пойдет не так.