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

Original author: Ivan Jovanovic

Нетрудно догадаться, что большинство разработчиков сейчас используют какие-либо фреймворки для разработки приложений. Они помогают нам структурировать сложные приложения и экономят время. Каждый день можно наблюдать большое количество обсуждений, какой фреймворк лучше, какой нужно учить, и тому подобное. Так что, в этот раз я поделюсь своим опытом и отвечу на вопрос: «Почему я перешел на 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? Убедил ли я вас попробовать? Дайте знать, что вы думаете в комментариях ставьте лайк, подписывайтесь на...!

Similar posts

Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 24

    +1
    RxJS + React дадут примерно то же самое, правильно понимаю? Ну и таки кажется, что без хранения состояния в явном виде всё равно в приложении не получится, без модели не обойтись (именно она и будет синхронизироваться с персистентными хранилищами и внешними ресурсами). Грубо говоря, Цикл.ЖС — это не фреймворк, а библиотека привязки ДОМа к модели через концепцию потоков событий. Примерно так видится ниша Цикла.
      0
      Возможно вы и правы, но сами создатели позиционируют его как фреймворк:
      A functional and reactive JavaScript framework for predictable code.
        +1
        (Называть Цикл.ЖС фреймворком или библиотекой мне не очень принципиально, хотелось бы подтверждения остальным догадкам, чтобы понять для себя, есть ли смысл «погружаться глубже» :).)
      +2
      К сожалению, остались не ясны плюсы перехода на Cycle.js. В качестве примера автор говорит о сотне компонентов в сотне контейнеров. Разве это минус реакта? Как описываемый инструмент позволит сделать работу с большой кодовой базой проекта проще? Аналогично и про потоки данных (да, действительно в реакте их нет, так реакт — это лишь библиотека для рендера).

      Сам же фреймворк лично у меня оставил впечатление jQuery в который добавили стримы и реактивности.
        0
        Сотни компонентов и контейнеров действительно тяжело поддерживать из-за того, что все это раскидано по директориям, об этом и говорит автор. Вопрос в том, насколько это решается в Cycle.js — вопрос открытый. Если вы имеете опыт в Cycle.js, было бы здорово, если бы вы им поделились ;)
          +1
          Опыта использования Cycle.js нет, но логика подсказывает, что если проект большой, то все равно будет много директорий и файликов :) Тут поможет модульность и грамотная архитектура проекта.
        +1

        Сама концепция этих потоков данных — это отличная идея. Помнится, ещё года два назад она полностью захватила мой мозг на пару дней, слишком уж она… Красива, что ли. В общем, это одна из тех по настоящему высокоуровневых идей, вроде ООП, которые действительно создают новые парадигмы программирования. Проблема лишь в том, что наверняка как обычно будет куча низкоуровневых задачек, которые в эту самую парадигму плохо ложатся и опять будут всякие хаки, грязь и прочая пена, неизбежно появляющаяся там, где безбрежный океан крутых идей разбивается о скалы быта...


        Впрочем, это я расчувствовался. Сам фреймворк выглядит любопытно, спасибо за расширение кругозора.

          0
          Есть у cicle.js что-то типо react-native?
          +4
          Мне кажется, что старое доброе приложение-счетчик, будет идеальным для этого примера.

          Обожаю такие обороты. Очевидно, оно идеально, потому что на чем-то более приближенном к действительности (сложном) cycle.js уже показывает себя не так выгодно?

            +5

            А я не поленился и добавил todomvc на нём в бенчмарк. Как-то он не показывает себя особо шустрым даже в таком тривиальном приложении. Не говоря уж о том, что ручное управление потоками — отстой.

              +1

              Так Cycle.js очень медленный, впрочем как и любой код на FRP ;]
              Кстати, а вы не пробовали, ради развлечения, на базе ваших атомов сделать rx-like либу?

                0

                Эм… а зачем?

                  0

                  Just for fun, ну и чтобы писать можно было писать в стиле «потоков», а с учётом реализации атомов, такая либ должна получиться на порядок шустрей.

                    0

                    С чего бы ей быть шустрее, если вручную оптимально заоркестировать потоки крайне сложно? Разве что за счёт автоматического distinctUntilChanged и debounce.

                      0
                      А ваши Атомы похожи на обсерваблы и трассировку зависимостей Нокаута? (Я, возможно, уже спрашивал, но вот сейчас появилась необходимость в такой библиотеке вне контекста привязки к DOM'у, возможно, имеет смысл взять вашу реализацию).
                        0

                        Да, под его впечатлением и создавалось, но с более эффективным механизмом актуализации.

                          0
                          (Спасибо, интересно, гляну в ближайшее время.)
              0
              Люди придумали фреймворки, чтобы установить систему общих правил и подходов при разработке. Это должно было помочь исключить разногласия по вопросам архитектуры и создать некий свой мини-стандарт типа: «вью пишем через модули с таким вот интерфейсом, контроллер/обсервер/медиатор у нас реагирует вот на эти эвенты, модели взаимодействуют с вью посредством колбэков, а шаблонизатор у нас будет вот такой».

              Все это прекрасно работает пока в отрасли существует 1-3 авторитетных фреймворка и программисты могут формировать довольно крупные сообщества приверженцев того или иного подхода, но когда количество фреймворков и их сочетаний с прочими инструментами разрастается до нескольких десятков, то это становится равносильно отсутствию каких либо стандартов.

              React, Angular, Backbone, Ember, Extjs и т.д.
              Добавьте сюда еще различные инструменты сборки и языки, компилируемые в JS.

              Черт, когда я встречаю фронтендера, то мне неочем с ним говорить — его стек инструментов и технологий настолько далек от того что мне знакомо и понятно))
                0
                Всегда можно поговорить о функциональном, реактивном, объектно-ориентированном подходах, как организовывать архитектуру директорий и файлов и прочем общем ;)
                  +1
                  Так их и есть всего 2 авторитетных: Angular и React. Backbone — умер в ходе эволюции. Ember еще пытается подавать признаки жизни, но популярность его падает. Ext JS стоит денег и ставить его сюда в один ряд не совсем корректно. Ну можно еще Vue.js сюда записать.

                  Реально живых языка тоже всего два: TypeScript и Dart. Все остальное умирает без надобности.
                  +1
                  Cycle.js. Она содержит 26 методов и весит 30kb. Это одна из самых быстрых библиотек для реактивного программирования на JS

                  Интересный у вас подход в определении производительности библиотек.


                  Я уже работал с RxJs, и нашел ее лишь бледным подобием RxJava. Скажите как обстоят дела у Cycle.js с поддержкой back pressure?

                    0
                    Для любителей FP и FRP есть Elm, у которого синтаксис несколько более приспособлен к функциональщине (хаскель на JS).
                      0

                      Всегда прикалывал такой подход: скажем что у фреймворка А есть такие то минусы, а потом скажем что у фреймворка Б есть такие то плюсы.
                      Где же само сравнение? Я вот так и не понял, почему это с Cycle.js кода в большом проекте будет меньше? Почему недоООП, которое разрабами представляется как функциональное — проблема?

                      Only users with full accounts can post comments. Log in, please.