- PVSM.RU - https://www.pvsm.ru -

Тонкости использования ReactJS в MeteorJS приложении

Я встречаю много статей, где описываются плюсы применения React вместе с Meteor. Ни разу не видел, чтобы кто-то шёл дальше «плюсов» и описал то, как, собственно, это сделать.

При имплементации возникает пара серьезных проблем.

Первая — перенос существующего фронтенда на React по частям, вторая — использование сторонних библиотек для React.

Решение проблем под катом.

Использование React компонентов в существующем Meteor приложении

Для того, чтобы установить поддержку React, достаточно поставить этот пакет: github.com/reactjs/react-meteor [1]

Он добавляет обработчик jsx файлов и глобальный объект React на фронтенд и на бекенд. Так же он добавляет свою обертку для компонентов, но она в данный момент плоха, так как не работает с существующим в Meteor роутингом.

Выражается это в том, что при удалении родительского Blaze шаблона (при переключении пути) React компонент как ни в чем не бывало остается на странице.

А данный момент я использую свою обертку, позволяющую сделать из React компонента Blaze шаблон, который можно включать в существующие шаблоны, по частям переводя фронтенд с Blaze на React.

Пока я это писал, в комментариях к англоязычной статье подсказали github.com/grovelabs/meteor-react [2]. Обновлю, как только протестирую.

Моя обертка выглядит так:

_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

Источник [3]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/meteor/89197

Ссылки в тексте:

[1] github.com/reactjs/react-meteor: https://github.com/reactjs/react-meteor

[2] github.com/grovelabs/meteor-react: https://github.com/grovelabs/meteor-react

[3] Источник: http://habrahabr.ru/post/256035/