[Из песочницы] Генератор гамм на Reactjs

Здравствуйте, хабражители!Не так давно на просторах интернета появился новый javascript framework от facebook — Reactjs. Данный фреймворк идеально подходит для создания простых и сложных javascript приложений. Позволяет организовывать ваш клиент-сайд в виде независимых компонентов. Берет на себя всю заботу по модификации DOM структуры дерева. И делает это весьма эффективно и разумно.

В общем, в результате небольшого знакомства с reactjs появилось такое приложение — demo. Цель данного поста поделиться впечатлениями от работы с reactjs + gruntjs + browserify.

Ниже будет изложено:

Основные моменты создания приложения и личные впечатления (симбиоз reactjs + browserify + gruntjs + coffeescript). Серверный пререндериг reactjs компонентов для статических страниц. Подход к сборке reactjs приложения с помощью gruntjs и деплой на gh-pages одной командой. Тех кого заинтересовал прошу под кат…

Идея приложения Не буду кривить душой, идея online генератора гамм не нова, и свое вдохновение я почерпнул из этого ресурсика, добавив несколько плюшек от себя. Гаммы, в первую очередь, полезны для людей, которые пытаются освоить импровизацию. И так же полезны как упражнение для усовершенствования техники игры. Каждая гамма, по сути, состоит из определенных нот, которые вычисляются по определенной формуле. Мне, как любителю гитары, доставляет удовольствие угадывать тональность и пытаться подбирать соло под любимую музыку. И данное приложение очень хорошо в этом помогает.Что было реализовано 7f455fb8c31f80ee37a90c4327ca58ae.pngВыбор тональности. Выбор гаммы (минор, мажор, блюз, пентатоника). Выбор гитарного строя. Поочередное воспроизведение нот для выбранного участка грифа гитары. Смена направления воспроизведения нот (сверху вниз/снизу вверх). Возоможность циклического воспроизведения (для заучивания). Автоматическая смена направления воспроизведения нот. Организация приложения Для хорошей кармы, да и просто люблю с этим повозиться — весь 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).В процессе создания приложения столкнулся с тем, что в интернете мало готовых решений для простых компонентов. Сначала меня это очень смущало и я тратил некоторое время на поиски готовых решений, например для тех же выпадающих списков. Пытался интегрировать их в свой проект и адаптировать верстку, но потом попробовал реализовать функциональность вот такого выпадающего списка с нуля:image

И был удивлен насколько это просто с 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

© Habrahabr.ru