javascript

Почему я перешел с React на Cycle.js

  • вторник, 11 июля 2017 г. в 03:12:39
https://habrahabr.ru/post/332674/
  • ReactJS
  • JavaScript



Нетрудно догадаться, что большинство разработчиков сейчас используют какие-либо фреймворки для разработки приложений. Они помогают нам структурировать сложные приложения и экономят время. Каждый день можно наблюдать большое количество обсуждений, какой фреймворк лучше, какой нужно учить, и тому подобное. Так что, в этот раз я поделюсь своим опытом и отвечу на вопрос: «Почему я перешел на Cycle.js с React?».


React, возможно, самый популярный frontend-фреймворк (на момент 2017) с огромным сообществом. Я большой фанат этого фреймворка и он мне помог изменить взгляд на веб-приложения и их разработку. Некоторые любят его, а кто-то считает, что он не так хорош.


Большинство использует React без мысли о том, что есть лучший инструмент или способ разработки веб-приложений. Это дало мне толчок попробовать Cycle.js, как новый реактивный фреймворк, который становится все более и более популярным изо дня в день.


И в этой статье я хочу объяснить:

  1. Что такое реактивное программирование
  2. Как работает Cycle.js
  3. И почему он, на мой взгляд, лучше 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 будет содержать только основной файл со скриптами, где и прописана вся логика.

npm install и продолжение настройки
Сначала надо создать новый 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

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? Убедил ли я вас попробовать? Дайте знать, что вы думаете в комментариях ставьте лайк, подписывайтесь на...!