Как следует из официальной документации, React.js — V из MVC, и, как правило, вместе с ним применяются другие решения, в данном случае — Backbone.js и Require.js. А еще Jasmine, Karma и Grunt. Сегодня я поделюсь наброском проекта с применением этих инструментов.
Ссылка для нетерпеливых.
Примерно так выглядит «дерево» проекта:
Подробно рассматривать весь исходный код не вижу смысла, для этого есть гитхаб, остановлюсь на ключевых моментах:
Собственно, сам роутинг. Комментарии, думаю, излишни.
В ui-components описываются обычные React-компоненты в синтаксисе .jsx и таблицы стилей для каждого отдельного компонента. Есть нечто общее с БЭМ. Каждый компонент лежит в отдельной папке и зависит только от самого React'а.
Не только компоненты интерфейса, но и контроллеры пишутся в синтаксисе .jsx, чтобы можно было сделать вот так:
Тестировать UI сложно, поэтому Facebook любезно предоставил TestUtils специально для тестирования React компонентов, тесты для которых могут выглядеть как-то так:
Код, который мы будем тестировать. Компонент, который рисует bootstrap панель с заголовком и содержимым.
А это — тесты для panel, написанные с применением Jasmine, можно использовать любой фреймворк который вам нравится, например, разработчики React используют Jest. Тесты запускаются при помощи Karma, к сожалению пока и не смог завести PhantomJS для этих тестов, так что приходится мириться с постоянно всплывающим хромом.
Кстати, index.html выглядит довольно коротко и аккуратно:
С ней прекрасно справляется grunt который «компилирует» less и jsx, прогоняет тесты, обновляет страничку в браузере при сохранении файлов и делает еще много прикольных вещей.
В принципе, любой компонент UI можно просто взять и скопировать в другой проект, вместе со стилями и тестами (разумеется, там тоже нужен React). И он(компонент) заработает сразу, без лишних телодвижений. Особенно это актуально это для админок и типовых компонентов, там даже стили менять не надо.
Во-первых, хотелось собрать все нужные инструменты в одном месте, чтобы они еще и работали. Во-вторых, я очень люблю React, использовать его с Backbone, наверное, стоит, оба легкие, шустрые и расширяемые, а Require может сделать структуру приложения прозрачнее. В-третьих получился (хочется верить) небольшой «шаблон» типового проекта, начиная разработку можно просто стянуть репозиторий и «всё сразу заработает (с)».
Всё и сразу пока не работает. В ближайших планах реализовать сборку проекта на продакшен, с минификацией всего, что можно минифицировать. В чуть более далеких — написание yoman генератора для скаффолдинга котроллеров и компонентов.
Ссылка для нетерпеливых.
Хотелки
- Прозрачная структура проекта;
- Автоматизация всей рутинной работы;
- Автоматизация тестирования;
- Модульность;
- Повторное использования кода;
- Производительность.
Чего добились
Примерно так выглядит «дерево» проекта:
. ├── 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. Тесты запускаются при помощи Karma, к сожалению пока и не смог завести 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 генератора для скаффолдинга котроллеров и компонентов.
