[Из песочницы] Генератор гамм на Reactjs
Здравствуйте, хабражители!Не так давно на просторах интернета появился новый javascript framework от facebook — Reactjs. Данный фреймворк идеально подходит для создания простых и сложных javascript приложений. Позволяет организовывать ваш клиент-сайд в виде независимых компонентов. Берет на себя всю заботу по модификации DOM структуры дерева. И делает это весьма эффективно и разумно.
В общем, в результате небольшого знакомства с reactjs появилось такое приложение — demo. Цель данного поста поделиться впечатлениями от работы с reactjs + gruntjs + browserify.
Ниже будет изложено:
Основные моменты создания приложения и личные впечатления (симбиоз reactjs + browserify + gruntjs + coffeescript). Серверный пререндериг reactjs компонентов для статических страниц. Подход к сборке reactjs приложения с помощью gruntjs и деплой на gh-pages одной командой. Тех кого заинтересовал прошу под кат…
Идея приложения
Не буду кривить душой, идея online генератора гамм не нова, и свое вдохновение я почерпнул из этого ресурсика, добавив несколько плюшек от себя. Гаммы, в первую очередь, полезны для людей, которые пытаются освоить импровизацию. И так же полезны как упражнение для усовершенствования техники игры. Каждая гамма, по сути, состоит из определенных нот, которые вычисляются по определенной формуле. Мне, как любителю гитары, доставляет удовольствие угадывать тональность и пытаться подбирать соло под любимую музыку. И данное приложение очень хорошо в этом помогает.Что было реализовано
Выбор тональности.
Выбор гаммы (минор, мажор, блюз, пентатоника).
Выбор гитарного строя.
Поочередное воспроизведение нот для выбранного участка грифа гитары.
Смена направления воспроизведения нот (сверху вниз/снизу вверх).
Возоможность циклического воспроизведения (для заучивания).
Автоматическая смена направления воспроизведения нот.
Организация приложения
Для хорошей кармы, да и просто люблю с этим повозиться — весь javascript был оформлен в виде commonjs модулей, склеивается в 1 файл и минифицируется а reactjs-компоненты пререндерятся во время сборки и вставляются в html странички.Данный эффект достигался при помощи таких инструментов:gruntjs — сборка и деплой проэкта
browserify — для работы commonjs модулей в браузере
grunt-react-prerender — пререндеринг компонентов на сервере
Формат commonjs очень удобен и позволяет использовать один и тот же javascript код как на сервере так и на клиенте.Серверный пререндеринг
Одной из наиболее привлекательных возможностей в reactjs для меня является возможность серверного пререндеринга компонентов. Ведь прекрасно же иметь возможность инициализировать начальное состояние вашего приложения и отрисовывать копоненты до того как они попадают в браузер. Прелесть reacjs в том, что он сам понимает когда компонент уже отрисован, и не пытается сделать это снова. Если просмотреть код странички из demo, то видно что главный компонент страници уже был отрисован до отдачи контента в браузер. Главным требованием для серверного рендеринга является возможность сделать require компонента на сервере. Поскольку процедура пререндеринга является необходимой только при деплое — решил сделать под нее отдельный grunt plugin, который будет обрабатывать html файлы перед загрузкой на gh-pages — grunt-react-render.Плагин достаточно прост и действует по такому алгоритму:Читаем файл для обработки.
Парсим html структуру, находим теги с атрибутом data-rcomp.
Считываем относительный путь к компоненту из атрибута data-rcomp.
Делаем require компонента и вызываем метод react.renderComponentToString().
Вставляем внутрь тега с атрибутом data-rcomp.
Сохраняем файл.
Вот участок html кода главной страници, которая обрабатывается этим плагином:
<div class="container"> <h1>Scales generator</h1> <div data-rcomp="./lib-js/pages/scales_page_component" id="container"> </div> </div>
Так выглядит grunt конфигурация плагина:
react_render:
index:
options:
src: '.dist/index.html'
Впечатления о Reactjs
Reactjs подталкивает к тому, чтобы формировать логику приложения в виде отдельных конфигурируемых компонентов, которые по возможности, можно повторно использовать. Философия reactjs говорит о том, что компоненты должны хранить по минимуму состояния, так как хранимое состояние в разных местах — это источник магии и side-эффектов. Поэтому, лучше сохранять и изменять state в компонентах верхнего уровня и передавать внутренним компонентам через свойства (props).В процессе создания приложения столкнулся с тем, что в интернете мало готовых решений для простых компонентов. Сначала меня это очень смущало и я тратил некоторое время на поиски готовых решений, например для тех же выпадающих списков. Пытался интегрировать их в свой проект и адаптировать верстку, но потом попробовал реализовать функциональность вот такого выпадающего списка с нуля:
И был удивлен насколько это просто с reactjs. По сути, компонент становится очень прост, когда весь рендеринг берет на себя фреймворк, а тебе остается только задать то, как нужно отрисовать компонент при разном состоянии и правильно его переключать.
код выпадающего списка React = require 'react' {ul, li, span, div, a} = React.DOM
SimpleDropdown = React.createClass displayName: "SimpleDropdown" getDefaultProps: -> onChange: ->
getInitialState: -> isOpen: false value: @props.value or ""
toggle: -> @setState {isOpen: !@state.isOpen}
itemClick: (ev) -> ev.preventDefault() value = (ev.target.getAttribute "value") @setState {value, isOpen: false} @props.onChange value
render: -> self = @ options = @props.options.map ([value, text]) -> (li {key: "opt_#{value}"}, (a {value, href: "#", onClick: self.itemClick}, text))
openCls = if @state.isOpen then "open" else ""
currentOption = (@props.options.filter ([value, text]) => value.toString() is @state.value.toString())?[0]
(div className: "btn-group #{openCls}" (button className: "btn btn-default" onClick: @toggle (span {className: "glyphicon"}, "") currentOption?[1] (span {className: "caret"}, "")) (ul className: "dropdown-menu" ref: "select" options)) Coffeescript оказался очень удобен для описания html структуры компонентов. Особенно полезно его реструктурирующие присваивание. {div, ul, span, li} = React.DOM
(div {className: "div"} (span {className: "span", "span Text") (ul (li "option 1") (li "option 2") ) Как по мне, вполне достаточно чтобы не использовать jsx. Так же пришлось повозиться с bootstrap кнопками и draggable поведением компонентов.
Бизнес-логика Так как приложение создавалось не только в целях освоить рекат, а и упростить себе жизнь и другим в изучении гамм на гитаре — всю логику старался делать как можно проще для того, чтобы при возможности, кто-то мог без особых усилий добавить другую гамму или строй гитары. Так вот, например, выглядит описание гамм в коде: SCALES = Minor: desc: "Minor scale" size: [STEP, hSTEP, STEP, STEP, hSTEP, STEP, STEP] get_notes: (Tonica) -> generate_scale Tonica, SCALES.Minor
Major: desc: "Major scale" size: [STEP, STEP, hSTEP, STEP, STEP, STEP, hSTEP] get_notes: (Tonica) -> generate_scale Tonica, SCALES.Major ... Каждая гамма имеет свою формулу(size), функцию для генерации нот и описание. Показав этот проект одному другу, он имея более глубокие познания в музыке, без особого труда добавил несколько других гамм.Так же просто выглядят данные для гитарных строев:
TUNINGS = "Standart": name: "Standart E" notes: [E, B, G, D, A, E] offset: [0, 0, 0, 0, 0, 0]
"DropD": name: "Dropped D" notes: [E, B, G, D, A, D] offset: [0, 0, 0, 0, 0, -2] ... Поочередное воспроизведение звуков реализовано с помощью библиотеки howler.js.Если у кого-то из тех кто дочитал до этого места появится желание добавить и усовершенствовать что-то в проекте — буду этому безумно рад.
Сборка и деплой 1й командой Многие совсем не заморачиваются над сборкой проекта и организацией кода, может это и правильно. Лично я получаю от процесса автоматизации удовольствие, поэтому немного заморочился над этим и организовал сборку и деплой проекта на gruntjs. Есть 3 основных команды:grunt build (компиляция coffeescript, обертка в commonjs модули для браузера, склейка js и css в 1 файл) grunt deploy (build + минификация js и css, копирование в дирректорию для деплоя, пререндеринг реакт компонентов) grunt deploy-gh (deploy + деплой на github pages) Подробнее о сборке проекта может рассказать Gruntfile в репозитории.Выводы В целом, работа с reactjs оставила позитивное впечатление, в будущем хочу попробовать его вместе с какой-то FRP библиотекой(RxJs или bacon.js). Так же, планирую добавлять другой полезный функционал в этот проект.Благодарю за внимание! Любая критика, пожелания и отзывы — приветствуются.Исходный код — github
