※ @ANTON072 に指摘頂きましたが、crossroadsの扱いで、route変更時にイベントが発火しないパタンがあるので一部修正しましたm m
これはなに
SPAではない(コレなんて言えば良いんですかね?ググラビリティ低くて辛い...)普通のRailsアプリケーションで、ページごとに異なるReactのコンポーネントを描画する方法を試行してみました。
また、速度向上の影響はかなり美味しいので、Turbolinksにも対応してみました。
環境
- rails 5
- react 15.2.1
- crossroads 0.12.2
crossroadsは特定のurlで起動するコンポーネントの決定および起動のために使用。
なんでも良いとは思うのですが、turbolinksがhistory apiにstateをガンガンpushするので、そこに影響が無いものが良いと思います。
つくってみる
Rails
とりあえず行き来できれば良いので、2ページ作成する
dummy_index GET /dummy(.:format) dummy#index
dummy GET /dummy/:id(.:format) dummy#show
#react-component
= link_to 'go to show page', dummy_path(1)
#react-component
= link_to 'go to index page.', dummy_index_path
ReactDOMをマウントするためのidを用意しておく
React
※turbolinksに乗るので、JSファイルはwebpackとかでまとめてapplication.htmlのheaderで読み込まれること。
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as crossroads from 'crossroads';
/* 手っ取り早くコンポーネントを2個用意しておく */
const Hello = () => {
return (
<p>hello</p>
);
};
const World = () => {
return (
<p>world</p>
);
};
let router;
const createRouter = () => {
let router = crossroads.create();
router.addRoute('/dummy', () => {
render(<Hello />);
});
router.addRoute('/dummy/{id}', (id) => {
render(<World />);
});
return router;
};
/* turbolinks5により、ページ遷移毎に発火 */
document.addEventListener('turbolinks:load', event => {
const render = (dom) => {
const target = document.querySelector('#react-component');
ReactDOM.render(dom, target);
};
if (!router) {
router = createRouter();
}
// こちらを実行しないと、同一ページに対するリンクの場合、crossroadsが発火しない
router.resetState();
router.parse(window.location.pathname + window.location.search);
});
動作確認
リンクをポチポチして遷移後、chromeにてReact Developer Toolsにて確認
コンポーネントがGCされてませんね。
対策
turbolinks#full-list-of-events
こちらのturbolinksのイベントリストを参考に、画面遷移時にコンポーネントをunmountすれば良いのでは?
ということで対策版のコードです
...略
document.addEventListener('turbolinks:visit', event => {
ReactDOM.unmountComponentAtNode(
document.querySelector('#react-component')
);
});
最初は名前的にbefore-visitだろうと思ったのですが、historyでの遷移時には発火しないのでこちらを採用しました。
まとめ
turbolinksの恩恵を受けるためには多少ライフサイクル毎の処理を書く必要がありますね。
また、JavaScriptが初回しかロードされないので、eventListenerやtimeout系のメソッドにも気を使わないといけません。
それと、SPAではないアプリケーションにおいて、任意のページに任意のコンポーネントを起動する良いサンプルが見つからなかったのでこういう実装になりましたが、イケてる方法があったら是非教えていただきたいです!