React.js это новый популярный игрок из команды “Фреймворки JavaScript”, и он отличается своей простотой. Там где другие фреймворки реализуют полноценный MVC, можно сказать что React реализует только V (причем многие заменяют V в своих фреймворках на React). Приложения на React строятся на двух основных принципах: Компоненты (Components) и Состояния (States). Компоненты могут быть сделаны из других компонентов поменьше, встроенных или кастомных; Состояния это, как называют его ребята из Facebook1 - one-way reactive data flow, означает что наш UI будет реагировать на каждое изменение состояния.
Обзор
От переводчика: Оригинал статьи
Одна из хороших вещей в React это то, что он не требует дополнительных зависимостей, что делает его подключаемым с практически любой JS библиотекой. Воспользовавшись этой функцией, мы собираемся включить его в наш Rails стек и построить frontend-powered приложение, или, если пожелаете, Rails вьюхи на стероидах.
Макет приложения
В данной статье, мы будем создавать с нуля маленькое приложение для отслеживания затрат; каждая запись будет содержать дату, заголовок и сумму. Записи будут делиться на Кредит (Credit) если сумма больше нуля и Дебет (Debit) в обратном случае. Вот макет проекта:
Суммируя, в приложении будут следующие кейсы:
- Когда пользователь создает новую запись через горизонтальную форму, она (запись) будет добавляться в таблицу со всеми записями
- Пользователь сможет построчно редактировать любую запись
- Клик по любой кнопке Delete будет удалять связанную запись из таблицы
- Добавление, редактирование и удаление записей будет обновлять сумму в окне на верху страницы
Создаем приложение
Любое приложение начинается с простых вещей. Создадим наше приложение и назовем его, например, Accounts
:
Я рекомендую использовать RVM для управления версиями Ruby и для каждого приложения отдельный gemset, подробнее можно посмотреть в этой статье.
Для UI нашего проекта будет использован Twitter Bootstrap. Процесс установки bootstrap немного выходит из рамок данного how-to, вы можете установить например официальный гем bootstrap-sass
следуя инструкции или использовать rails-assets.
Когда наш проект инициализирован, нужно добавить в него React. В данной записи мы будем устанавливать официальный гем react-rails потому что будем использовать некоторые крутые фишки, реализованные в данном геме, но есть и другие способы выполнить эту задачу, например все теже rails-assets или можно скачать исходники с официального сайта и разместить их в паке javascrips
.
Если вы до этого имели дело с Rails, то вы знаете как легко добавить гем в проект, добавим нужный нам гем react-rails
в Gemfile:
Затем, естественно, устанавливаем новые гемы:
react-rails
идет с установочным скриптом, который создаст файл component.js
и каталог components
в папке app/assets/javascripts
где собственно и будут жить наши компоненты React.
Если после процесса установки вы загляните в файл application.js
то найдете там три новых линии:
В основном, он включает актуальную react библиотеку, манифест components
и ujs
. Как вы могли догадаться для имен файлов react-rails включает ненавязчивый JavaScript драйвер, который поможет нам монтировать React компоненты и будет поддерживать события Turbolinks.
Создаем ресурс
Мы создадим ресурс Record
, который будет включать поля date
, title
и amount
. Вместо использования генератора scaffold
, мы будем использовать генератор resource
, потому что нам не понадобятся все файлы и методы, которые создаются при исползовании scaffold
.
Чуть чуть магии и у нас будет готовый ресурс Record
, включающий модель, контроллер и маршруты. Остается только создать базу данных и запустить миграции.
Дополнительно вы можете создать несколько записей в базе данных используя rails console
:
Не забудьте запустить сервер с помощью команды rails s
.
Ура! Теперь мы можем кодить.
Вложенные компоненты: список записей
Нашей первой задачей будет рендерить любые записи в таблице. Для начала, нужно создать экшен index
в нашем контроллере Records
:
Далее, нам нужно создать новый файл index.html.erb
в папке app/views/records/
, этот файл будет мостом между нашим Rails приложением и React компонентами. Чтобы достигнуть этого мы будем использовать хелпер метод react_component
, который получает имя компонента React, который мы хотим отрендерить вместе с данными которые мы хотим в него передать.
Стоит отметить, что этот хелпер предоставляется гемом react-rails
и если использовать другие способы интеграции React в Rails приложение, то он не будет работать.
Теперь вы можете открыть http://localhost:3000/records в браузере. Очевидно, что сейчас ничего работать не будет, потому что у нас просто напросто нет React компонента Records, но если мы посмотрим в код сгенерированной страницы, то увидим примерно следующее:
C такой разметкой, react_ujs
видит что мы пытаемся рендерить React компонент и будет инициализировать его, включая свойства, которые мы передали через react_component
, в нашем случае содержимое @records
.
Пришло время сделать наш первый React компонент. В каталоге javascripts/components
создаем новый файл и называем его records.js.coffee
, этот файл и будет содержать наш компонент Records.
Каждый компонент должен содержать метод render
, который будет отвечать за рендеринг самого себя. Этот метод должен возвращать экземпляр класса ReactComponent, в этом случае, когда React выполнит ре-рендер, он (экземпляр) будет обработан оптимальным путем (React обнаруживает существование новых узлов путем создания виртуального DOM в памяти). В примере выше мы создали экзмепляр h2, встроенный ReactComponent.
Другой способ инициализировать ReactComponents внутри метода render через
JSX
синтаксис. Пример кода выше эквивалентент следующему:
Я рекомендую, если вы работаете с soffescript, использовать синтаксис React.DOM
вместо JSX
, потому что код будет выстраиваться иерархично, как, например, в haml. Однако, если вы интегрируете React в существующее приложение с erb, вы можете реюзать уже существующий код конвертируя его в JSX.
Теперь обновим страницу в браузере.
Отлично! Мы отрендерили наш первый компонент React. Теперь настало время для отображения наших записей (records).
Кроме метода рендеринга, React компоненты имеют способность общаться между собой и сообщать свое состояние, чтобы определить необходим ре-рендеринг или нет. Нам нужно инициализировать состояния наших комопнентов и свойства с требуемыми значениями:
Метод getDefaultProps
будет выставлять свойства нашего компонента в случае, если мы забыли отправить какие-либо данные при его инициализации, и метод getInitialState
будет генерировать начальное состояние наших компонентов. Теперь нам нужно отобразить записи предоставленные нам вьюхой Rails.
Похоже, нам нужен хелпер чтобы форматировать строку с суммой (amount), мы можем написать простой форматтер строк и сделать его доступным для всех наших coffee файлов. Создадим новый файл utils.js.coffee
в каталоге javascripts/
со следующим содержимым:
Нам нужно создать новый компонент Record чтобы отображать каждую отдельную запись, создадим новый файл record.js.coffee
в папке javascripts/components
и запишем в него следующий контент:
Компонент Record будет отображать строку таблицы содержащую ячейки для каждого аттрибута записи. Не волнуйтесь об этих null в вызовах React.DOM.*, это означает что мы не отправляем атрибуты в компоненты. Теперь обновим метод рендер в компоненте Records следующим кодом:
Мы создали таблицу со строкой заголовком и внутри тела таблицы мы создали элемент Record для каждой существующей записи. Другими словами, мы угнездили встроенный и кастомный React компоненеты. Круто, да?
Чтобы React не тратил много времени на обновление нашего UI, при создании элемента Record, вместе с ним мы посылаем ключ: record.id
. Если мы так не сделаем, то увидим предупреждение в консоли браузера (и скорее всего получим головную боль в дальнейшем).
Вы можете посмотреть на результирующий код этой секции здесь или только изменения здесь.
Родитель-Потомок коммуникация: Создание записей
Теперь когда мы отображаем все имеющиеся записи, будет неплохо добавить форму для создания новых записей, давайте добавим эту фичу в наше React/Rails приложение. В начале, нам нужно добавить метод в наш Rails контроллер (не забываем использовать strongparams
):
Теперь, нам нужно создать React компонент чтобы обрабатывать создание новых записей. Компонент будет иметь собственное состояние чтобы хранить дату, заголовок и стоимость. Создадим новый файл record_form.js.coffee
в каталоге javascript/components
со следующим кодом:
Ничего фантастического, просто Bootstrap инлайн форма. Обратите внимание как мы объявляем атрибут value
для установки значения инпута и атрибут onChange
чтобы привязать метод обработчика, который будет вызываться на каждое нажатие клавиши. Метод обработчика handleChange
будет использовать имя атрибута чтобы определить какой инпут запустил событие и обновлять соответсвтующее значение состояния:
Мы используем интерполяцию строк чтобы динамически определять ключи объектов, эквивалент @setState title: e.target.value
когда name
равно title
. Но зачем нам использовать @setState
? Почему мы не можем просто засетить желаемое значение для @state
как мы обычно это делаем в регулярных JS объектах? Потому что @setState
будет выполнять 2 действия:
- Обновлять состояния компонента
- Запускать проверку/обновление UI на основе нового состояния
Очень важно держать это в голове каждый раз когда мы используем state
внутри наших компонентов.
Давайте посмотрим на кнопку submit
, в самом конце нашего метода render
:
Мы определили атрибут disabled
со значением !@valid()
, что означает что мы напишем метод valid
, который будет проверять что данные, переданные пользователем, корректные.
Для простоты мы проверяем @state
на пустые строки. Таким образом, кнопка Create
будет включаться и выключаться в зависимости от того есть ли данные в полях.
Теперь когда у нас есть контроллер и готова форма, время отправлять наши новые записи на сервер. Нам нужно событие для обработки формы, добавим атрибут onSubmit
в нашу форму и handleSubmit
метод (таким же образом мы обрабатывали событие onChange
ранее):
Давайте разберем новый метод построчно:
- предотвращаем отправку html формы
- постим (POST) данные новой записи на текущий URL
- колбек в случае успеха (success callback)
Success callback это главная часть процесса, после успешного создания новой записи мы будем уведомлены об этом событии и state
восстановится в свое дефолтное значение. Помните я упоминал что компоненты общаются между собой через @props? Вот, это оно. Наш текущий компонент отправляет данные обратно в родительский компонент через @props.handleNewRecord
чтобы уведомить его о существовании новой записи.
Как вы уже догадались, везде где мы создаем элемент RecordForm
нам нужно передавать свойство handleNewRecord
c возвращающим методом, что-то вроде React.createElement RecordForm, handleNewRecord: @addRecord
. У нашего родительского компонента Records
есть состояние со всеми существующими записями, нам нужно обновить его согласно добавленной записи.
Добавим новый метод addRecord
в файле records.js.coffee
и создадим новый элемент RecordForm
, сразу после заголовка h2 (внутри метода render
)
Обновите вкладку, заполните форму новой записью и нажмите Create
… Никаких задержек, запись добавилась незамедлительно и форма очистилась после сабмита, обновите страницу снова, чтобы убедиться что бекенд сохранил новые данные.
Если вы использовали другие JS фреймворки с Rails (например AngularJS) чтобы реализовать похожие функции, вы могли столкнуться с проблемой отлупа вашего POST запроса потому что он не содержит CSRF токен, который требует Rails. Почему мы не столкнулись с этим сейчас? Все просто, мы используем jQuery
чтобы общаться с бекендом, и jquery_ujs
драйвер будет добавлять CSRF токен в каждый AJAX запрос за нас. Круто!
Вы можете посмотреть на результирующий код этой секции здесь или только изменения здесь.
Реюзабельные компоненты: Индикаторы остатка
Какое приложение может быть без (милых) индикаторов? Давайте добавим блоки в верхней части с полезной информацией. Наша цель - показывать 3 значения: количество кредитных средств (total credit), количество дебетовых средств (total debit) и баланс (balance). Кажется что это работа для 3 компонентов или, может быть, для одного но со свойствами?
Мы можем создать новый компонент AmountBox
, который будет получать три свойства: amount, text и type. Создадим новый файл amount_box.js.coffee
в каталоге javascripts/components/
со следующим содержимым:
Мы используем элемент Bootstrap - панель, чтобы отображать инфорамцию блоками и установливать цвет через свойство type
. Мы также добавили простой форматтер - amountFormat, который читает свойство amount
и отображает его в формате валюты.
По хорошему, чтобы иметь законченное решение, нам нужно создать такой элемент (3 раза) внутри нашего основного компонента и отправлять в него требуемые свойства в зависимости от того что мы хотим отобразить. Давайте сначала сделаем метод калькулятор, откроем компонент Records
и добавим следующие методы:
credits
суммирует все записи со значением больше 0, debits
- суммирует все записи со значением меньше нуля и balance
говорит сам за себя. Теперь, когда методы вычислители на месте, нам просто нужно создать элементы AmountBox
внутри метода render
(сразу над компонентом RecordForm
)
Мы закончили с этой фишкой! Обновите страницу в браузере, вы должны увидеть три блока, отобаржющие суммы, которые мы вычислили ранее. Но погодите! Есть еще кое-что! Добавьте новую запись и посмотрите что произойдет…
Вы можете посмотреть на результирующий код этой секции здесь или только изменения здесь.
Вместо заключения
Так как материал получается довольно объемным, я решил разделить его на две части и это первая:
- React.js - tutorial для Rails разработчиков (часть 1)
- React.js - tutorial для Rails разработчиков (часть 2)
Вторая часть будет про setState/replaceState
, удаление и редактирование записей, рефакторинг и Reactive Data Flow
.
-
Соцсеть, признанная в России экстремистской ↩