Я встречаю много статей, где описываются плюсы применения React вместе с Meteor. Ни разу не видел, чтобы кто-то шёл дальше «плюсов» и описал то, как, собственно, это сделать.
При имплементации возникает пара серьезных проблем.
Первая — перенос существующего фронтенда на React по частям, вторая — использование сторонних библиотек для React.
Решение проблем под катом.
Использование React компонентов в существующем Meteor приложении
Для того, чтобы установить поддержку React, достаточно поставить этот пакет: github.com/reactjs/react-meteor
Он добавляет обработчик jsx файлов и глобальный объект React на фронтенд и на бекенд. Так же он добавляет свою обертку для компонентов, но она в данный момент плоха, так как не работает с существующим в Meteor роутингом.
Выражается это в том, что при удалении родительского Blaze шаблона (при переключении пути) React компонент как ни в чем не бывало остается на странице.
А данный момент я использую свою обертку, позволяющую сделать из React компонента Blaze шаблон, который можно включать в существующие шаблоны, по частям переводя фронтенд с Blaze на React.
Пока я это писал, в комментариях к англоязычной статье подсказали github.com/grovelabs/meteor-react. Обновлю, как только протестирую.
Моя обертка выглядит так:
_ReactUtils.createClass = function(opts) {
var templateName = opts.templateName;
var templateClass = new Template(
templateName,
function() {
return new HTML.DIV;
}
);
Template[templateName] = templateClass;
var Component = React.createClass(opts);
templateClass.onRendered(function() {
var template = this;
var data = this.data || {};
var c = React.createElement(Component, data);
c._meteorTemplate = template;
template._reactComponent = React.render(c, template.firstNode);
});
templateClass.onDestroyed(function() {
var template = this;
React.unmountComponentAtNode(template._reactComponent.getDOMNode());
});
return Component;
};
И позволяет создать Blaze шаблон из React компонента, при этом компонент будет знать о том, уничтожился ли шаблон и в этом случае удаляться со страницы.
Так же названные выше библиотеки предлагают свои варианты получения данных компонентом из рективных источников (Mongo курсоры, которые связывают бекенд и фронтенд Метеора, или Session/ReactiveVars, которые могут быть использованы для коммуникации внутри фронтенд приложения).
Так как в данный момент я использую первую библиотеку и ее вариант мне не нравится, я просто подписываюсь на реактивные источники сам, напрямую или через сервисы (в сервисах использую BaconJS, чтобы возвращать Streams а данными).
Другим, более «чистым» вариантом, было бы использование ванильного flux'a или reflux / fluxxor / любые другие его имплементации. Я не буду описывать это в деталях, так как пока толком не разобрался сам.
Сейчас делаю так:
componentWillMount: function() {
this._cancelSubscription = Tracker.autorun(...);
}
componentWillUnmount: function() {
this._cancelSubscription();
}
Использование сторонних React библиотек в Meteor
Главная проблема здесь в том, что, чтобы использовать какую-либо библиотеку в Meteor, её надо либо засунуть в код твоего приложения (сразу отбрасываем), либо сделать специальный Meteor Package.
Meteor Packages необходимы, но неудобны (для создателей, не для пользователей) по двум причинам.
Первая — это необходимость хранить в репозитории твоего пакета собранную библиотеку. Она не соберется у пользователей, и необходимо делать эту сборку самостоятельно каждый раз, как хочешь обновить исходный код библиотеки, при этом храня эту сборку в репозитории.
Вторая — связанная непосредственно с React, а, вернее, с повсеместным спользованием в React библиотеках webpack/browserify билдов — Meteor пакет не умеет работать с this. Экспортировать объект библиотеки надо глобально, в стиле:
MyLibrary = {...};
Это обязательное требование, и пока что разработчики не собираются его менять.
Webpack/Browserify же делают так:
(function(...){...})(this, ...);
И это не работает. Каждый билд вручную необходимо вбивать специальный костыль в стиле
var meteorHack = {
React: React
};
(function webpackUniversalModuleDefinition(root, factory) {
...
})(meteorHack, ...) // вместо this - meteorHack!
...
MyLibrary = meteorHack.MyLibrary;
Тогда все будет хорошо. Обращаю внимание, что React в билде игнорируется, так как мы зависим от пакета, который его уже инициализировал:
Package.onUse(function(api) {
api.use('reactjs:react@0.2.1', ['client', 'server']); // может быть, здесь я позже использую другую библиотеку, выкинув reactjs:react@0.2.1, так как он довольно несуразный
api.addFiles('dist/meteor-dist.js', ['client', 'server']);
api.export('MyLibrary', ['client', 'server']);
});
Соответственно, при билде React так же игнорируется, пример для webpack:
module.exports = {
entry: "./index.js",
output: {
path: __dirname,
filename: "dist/meteor-dist.js",
libraryTarget: "umd",
library: "MyLibrary"
},
externals: {
react: 'React'
}
};
Моей последней рекомендацией будет форкать библиотеку, которую хочется перенести в Meteor, а не создавать свой собственный репозиторий. Так чище.
Автор: Firfi