Як ми готуємо React, Require і Backbone

Як випливає з офіційної документації, React.js — V з MVC, і, як правило, разом з ним застосовуються інші рішення, в даному випадку — Backbone.js і Require.js. А ще Jasmine, Karma і Grunt. Сьогодні я поділюся начерком проекту з застосуванням цих інструментів.

Посилання для нетерплячих.

Хотілки
  • Прозора структура проекту;
  • Автоматизація всієї рутинної роботи;
  • Автоматизація тестування;
  • Модульність;
  • Повторне використання коду;
  • Продуктивність.

Чого домоглися
Приблизно так виглядає «дерево» проекту:

.
├── app
│ ├── app.js # Головний файл програми
│ ├── bower_components # Залежності, описані в bower.json
│ │ └── ...
│ ├── index.html 
│ ├── scripts
│ │ ├── controllers # Backbone контролери 
│ │ │ └── src
│ │ │ ├── hello.jsx
│ │ │ ├── main.jsx
│ │ │ └── notfound.jsx
│ │ ├── router.js # Конфігурація роутінга
│ │ └── ui-components # React компоненти
│ │ └── src 
│ │ └── panel
│ │ ├── panel.jsx
│ │ └── panel.less
│ └── styles # Стилі
│ └── src
│ └── main.less
├── bower.json # Опис залежностей
├── Gruntfile.js 
├── install-deps.bat # Скрипти, 
├── install-deps.sh # встановлюють
├── install-env.bat # залежно
├── install-env.sh # оточення
├── package.json # залежно робочого оточення node.js на продакшені не потрібні(Звичайно, якщо серверна частина не на node.js)
├── server 
└── test # Тести
├── test.config.js
└── ui-components
└── src
└── panel.test.jsx

А так код
Докладно розглядати весь вихідний код не бачу сенсу, для цього є гитхаб, зупинюся на ключових моментах:

/*
* app/app.js
* Тільки цей файл підключається в index.html все інше робить require
* Описує шляхи до файлів проекту і запускає роутинг(Backbone).
*/
'use strict';
requirejs.config({
baseUrl: './',
paths: {
app: './scripts',
controllers: './scripts/controllers/dest', // dest - папки з результатами "компіляції" .jsx і .less
ui: './scripts/ui-components/dest', // В системі контролю версій не зберігаються
underscore: './bower_components/underscore/underscore',
backbone: './bower_components/backbone/backbone',
jquery: './bower_components/jquery/dist/jquery.min',
react: './bower_components/react/react'
}
});


Власне, сам роутинг. Коментарі, думаю, зайві.

/*
* app/scripts/router.js
*/
'use strict';
define(function(require) {
var Backbone = require('backbone');

var AppRouter = Backbone.Router.extend({
routes: {
": 'MainCtrl',
'hello/:name(/)': 'HelloCtrl',
'*actions': 'NotFoundCtrl'
},

MainCtrl: require('controllers/main'),
HelloCtrl: require('controllers/hello'),
NotFoundCtrl: require('controllers/notfound')
});

return new AppRouter();
});


У ui-components описуються звичайні React-компоненти в синтаксисі .jsx і таблиці стилів для кожного окремого компонента. Є щось спільне з БЕМ. Кожен компонент лежить в окремій папці і залежить тільки від самого React'а.

Не тільки компоненти інтерфейсу, але і контролери пишуться синтаксис .jsx, щоб можна було зробити ось так:

/*
* app/scripts/controllers/src/hello.jsx
*/
'use strict';
define(['react', 'ui/panel/panel'], function(React, Panel){
/* Аргумент з рядка запиту */
return function(name){
/*
* Реалізуємо логіку програми, наприклад, надсилаючи запит до сервера.
* А потім рендерим компонент(и).
*/
React.render(
<Panel title="Hello controller">
<h1>Hello, {name}!</h1>
</Panel>, document.body);
};
});
.

Тести
Тестувати UI складно, тому Facebook люб'язно надав TestUtils спеціально для тестування React компонентів, тести для яких можуть виглядати якось так:

Код, який ми будемо тестувати. Компонент, який малює bootstrap панель з заголовком і змістом.

/*
* app/scripts/ui-components/src/panel.jsx
*/
define(['react'], function(React){
'use strict';
var Panel = React.createClass({
render: function(){
return (
<div className="panel panel-default">
<div className="panel-heading">
<h1>{this.props.title}</h1>
</div>
<div className="panel-body">
{this.props.children}
</div>
</div>);
}
});

return Panel;

});


А це — тести для panel, написані з застосуванням Jasmine, можна використовувати будь-фреймворк який вам подобається, наприклад, розробники React використовують Jest. Тести запускаються за допомогою Facebook, на жаль поки і не зміг завести PhantomJS для цих тестів, так що доводиться миритися з постійно спливаючим хромом.

/*
* test/ui-components/src/panel.test.jsx
*/
'use strict';
define(['react', 'ui/panel/panel'], function(React, Panel) {
describe('Panel behaviour tests', function() {
var TestUtils = React.addons.TestUtils;
var panel;
var p;
/*
* Аналог this.setUp() з xxxUnit
*/
beforeEach(function(){
panel = TestUtils.renderIntoDocument((
<Panel title="Test">
<p>Paragraph content</p>
</Panel>));
});

/* Перевіряємо що компонент взагалі рендерится */
it('Should render itself into DOM', function(){
expect(TestUtils.isCompositeComponent(panel)).toBe(true);
});

/* І що заголовок, переданий атрибутом відображається */
it('Should render title from props', function(){
var h1 = TestUtils.findRenderedDOMComponentWithTag(panel, 'h1');
expect(h1.getDOMNode().innerHTML).toBe('Test');
});

/* А також нащадки нікуди не зникли */
it('Should render children from props', function(){
var paragraph = TestUtils.findRenderedDOMComponentWithTag(panel, 'p');
/* 
* Specific react feature, it does not render text node directly, 
* but renders <span ... >Paragraph content</span>
*/
expect(paragraph.getDOMNode().innerHTML).toContain('Paragraph content');
});

});
});


До речі, index.html виглядає досить коротко і акуратно:

<html>

<head>
<title>React+Backbone</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="styles/dest/styles.css">
</head>

<body>
<div id="main"></div>
<script type="text/javascript" src="bower_components/requirejs/require.js"></script>
<script type="text/javascript" src="app.js"></script>
<script src="//localhost:35729/livereload.js"></script>
</body>

</html>


Автоматизація
З нею чудово справляється grunt, який «збирає» less і jsx, проганяє тести, оновлює сторінку в браузері при збереженні файлів і робить ще багато прикольних речей.

Повторне використання і модульність
В принципі, будь-який компонент UI можна просто взяти і скопіювати в інший проект, разом зі стилями і тестами (зрозуміло, там теж потрібен React). І він(компонент) запрацює відразу, без зайвих рухів. Особливо це актуально це для админок і типових компонентів, там навіть стилі міняти не треба.

І навіщо все це потрібно?
По-перше, хотілося зібрати всі потрібні інструменти в одному місці, щоб вони ще й працювали. По-друге, я дуже люблю React, використовувати його з Backbone, напевно, варто, обидва легені, спритні і розширювані, а Require може зробити структуру програми прозоріше. По-третє вийшов (хочеться вірити) невеликий «шаблон» типового проекту, починаючи розробку можна просто стягнути репозиторій і «всі одразу запрацює ©».

І що далі?
Усе й одразу поки не працює. У найближчих планах реалізувати складання проекту на продакшн, з минификацией все, що можна минифицировать. У трохи більш далеких — написання yoman генератора для скаффолдинга котроллеров і компонентів.

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.