この記事の内容
既存Railsアプリを徐々にSPAに置き換えていく、という課題を研修でやりました。
Reactの設定ファイルは先輩が書いてくださったので、その内容を読み解きながら、RailsアプリにReactを導入していく流れを確認しようと思います。
なお、実行環境は以下の通りです。
- Rails 6.0.3
- React 17.0.2
Rails6系なのでWebpacker
はデフォルトで入っているものとします。
ディレクトリ構成
今回の(最終的な)ディレクトリ構成は下記の通りです。関連のあるところだけ記載しています。なお、ここに記載のファイル以外に、config/routes.rb
も編集しています。
.
├── controllers
│ └── spa
│ └── spa_roots_controller.rb
└── javascript
├── App.jsx
├── Routes.jsx
├── components
│ └── Bar.jsx
├── packs
│ ├── application.js
│ ├── root_application.jsx
│ └── server_rendering.js
└── pages
└── Foo.jsx
ゴール画面
今回のゴール像は以下のイメージです。
Foo
ページの中にBar
コンポーネントを表示したいと思います。
Reactをインストール
まず、gem react-rails
をインストールします。
gem 'react-rails'
このGemを利用する以外の方法も調べはしたのですが、今回はコードの読み解きに留めることにしました。
なお、このgemは先に紹介した記事によると、「RailsのAsset Pipelineを利用してJSXをRailsが認識できる形に処理し」てくれるそうです。
公式サイトの記載通りに、コマンドを実行します。
$ bundle install
$ rails webpacker:install
$ rails webpacker:install:react
$ rails generate react:install
その結果、以下のファイルが生成・追記されます。
app/javascript/components/ # Reactのコンポーネント用に生成
app/javascript/packs/application.js # ReactRailsUJSの記載が追記
app/javascript/packs/server_rendering.js # サーバーサイドのレンダリング用に生成
ここで、application.js
とserver_rendering.js
には同じ内容が記載されていましたが...。
var componentRequireContext = require.context("components", true);
var ReactRailsUJS = require("react_ujs");
ReactRailsUJS.useContext(componentRequireContext);
require.context("components", true);
は、/component
ディレクトリ下の全てのファイルを拾うという設定、useContext(設定)
は、設定に書かれた要素がグローバルな要素として使われるという記載のようです。
それぞれ、以下を参考にしました。
RailsのルーティングとControllerの編集
次にRails側の、設定ファイルを書いていきます。
まずはconfig/routes.rb
に下記のように記載します。
scope 'spa' do
get '*path', to: 'spa_roots#show'
end
こちらの記述によって、/spa/*
で始まるリクエストが来たらなんでも、spa_roots
コンロトーラーのshow
アクションに飛ぶようにします。
(namespace
ではなくてscope
である理由は、先輩が参考記事を貼ってくれていました。)
そして、spa_routes_controller
には、ほぼ何も書かず...。
class SpaRootsController < ApplicationController
def show; end
end
代わりに、spa_roots#show
のViewの方にエントリーポイントとなるファイルを読み込む記載を入れます。
= javascript_pack_tag 'root_application'
この時読み込んでいるroot_application
には何が書かれているかというと、以下のとおりです。
import { App } from '../App'
import React from 'react'
import ReactDOM from 'react-dom'
document.addEventListener('DOMContentLoaded', () => {
ReactDOM.render(
<App />,
document.body.appendChild(document.createElement('div'))
)
})
ReactDOM.render
は以下のような形で使われ、React公式サイトによると、
ReactDOM.render(element, container[, callback])
渡された container の DOM に React 要素をレンダーし、コンポーネントへの参照(ステートレスコンポーネントの場合は null)を返します。
とのことですので、
document.body.appendChild(document.createElement('div')
の部分で、bodyの末尾に作られた<div>
タグに<App />
をレンダーさせます。
Routerの導入
では、先程記載したこの<APP />
には何が書かれているのかというと、以下の内容です。
import React from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import { Routes } from './Routes'
export const App = () => {
return (
<Router>
<Routes />
</Router>
)
}
Routerが出てきましたね。実は、順番が前後してしまいましたが、この少し前にreact-router-dom
をインストールしました。
yarn add react-router-dom
そして、app/javascript/App.jsx
ではBrowserRouter
(Routerという名前で使用)とRoutes
というライブラリを読み込んでいます。
それぞれのライブラリに関して深入りはしませんが、
-
BrowserRouter
... ルーティングの切り替え機能を提供 -
Routes
... それぞれのルーティングを提供
しているようです。
試しにページを表示
そして、この<Routes />
には次のように書いてありました。<Switch />
を使って、リクエストごとに表示する内容を切り替えていきます。
- APIドキュメント:Switch
import React from 'react'
import { Switch, Route } from 'react-router-dom'
import { Foo } from '~/pages/Foo'
export const Routes = () => {
return (
<Switch>
<Route path="/spa/foo">
<Foo />
</Route>
</Switch >
)
}
ここまでで、Foo
コンポーネントは表示されます。
この、Fooコンポーネント内で、さらにBarコンポーネントを呼び出せば、完成です。
import React from 'react'
import { Bar } from '~/components/Bar'
export const Foo = () => {
return (
<div>
<h1>これはfooページです。</h1>
<Bar />
</div>
)
}
完成!
以上で、RailsにReactを途中から導入する設定が完了です。
いやー、、、先輩いなかったら完全に彷徨っていたと思います。。。
まだまだJSはわからないことが多いので、これからも地道に調べながら理解を深めていきたいです。