Railsアプリケーションを途中からReactのSPAに書き換える際に設定したこと

## この記事の内容
既存Railsアプリを徐々にSPAに置き換えていく、という課題を研修でやりました。
Reactの設定ファイルは先輩が書いてくださったので、その内容を読み解きながら、RailsアプリにReactを導入していく流れを確認しようと思います。

なお、実行環境は以下の通りです。

- Rails 6.0.3
- React 17.0.2

Rails6系なので`Webpacker`はデフォルトで入っているものとします。

## ディレクトリ構成
今回の（最終的な）ディレクトリ構成は下記の通りです。関連のあるところだけ記載しています。なお、ここに記載のファイル以外に、`config/routes.rb`も編集しています。

```sh
.
├── 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
```
## ゴール画面
今回のゴール像は以下のイメージです。

[![Image from Gyazo](https://i.gyazo.com/8e096d786eaee8c1fb806b58b94a8a5f.png)](https://gyazo.com/8e096d786eaee8c1fb806b58b94a8a5f)

`Foo`ページの中に`Bar`コンポーネントを表示したいと思います。

## Reactをインストール
まず、gem `react-rails`をインストールします。

```ruby:Gemgile
gem 'react-rails'
```
[このGemを利用する以外の方法](https://qiita.com/k-penguin-sato/items/e3cc04f787cf3254cfae)も調べはしたのですが、今回はコードの読み解きに留めることにしました。

- 参考：[ReactをRailsと共に使う方法](https://qiita.com/k-penguin-sato/items/e3cc04f787cf3254cfae)

なお、このgemは先に紹介した記事によると、「RailsのAsset Pipelineを利用してJSXをRailsが認識できる形に処理し」てくれるそうです。

[公式サイトの記載](https://github.com/reactjs/react-rails#3-now-run-the-installers)通りに、コマンドを実行します。

```sh
$ 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`には同じ内容が記載されていましたが...。

```js:
var componentRequireContext = require.context("components", true);
var ReactRailsUJS = require("react_ujs");
ReactRailsUJS.useContext(componentRequireContext);
```
`require.context("components", true);`は、`/component`ディレクトリ下の全てのファイルを拾うという設定、`useContext(設定)`は、設定に書かれた要素がグローバルな要素として使われるという記載のようです。

それぞれ、以下を参考にしました。

- [Webpackでフォルダ内の全ファイルを一気にrequireする](https://qiita.com/jkr_2255/items/d23e66323857d3189a00)
- [React hooksを基礎から理解する (useContext編)](https://qiita.com/seira/items/fccdf4e73c59c491558d)


## RailsのルーティングとControllerの編集

次にRails側の、設定ファイルを書いていきます。
まずは`config/routes.rb`に下記のように記載します。

```ruby:config/routes.rb
scope 'spa' do
  get '*path', to: 'spa_roots#show'
end
```

こちらの記述によって、`/spa/*`で始まるリクエストが来たらなんでも、`spa_roots`コンロトーラーの`show`アクションに飛ぶようにします。

（`namespace`ではなくて`scope`である理由は、先輩が[参考記事](https://qiita.com/ryosuketter/items/9240d8c2561b5989f049)を貼ってくれていました。）

- [Railsのroutingにおけるscope / namespace / module の違い](https://qiita.com/ryosuketter/items/9240d8c2561b5989f049)

そして、`spa_routes_controller`には、ほぼ何も書かず...。

```ruby:app/controllers/spa_roots_controller.rb
class SpaRootsController < ApplicationController
  def show; end
end
```
代わりに、`spa_roots#show`のViewの方にエントリーポイントとなるファイルを読み込む記載を入れます。

```ruby:app/views/spa/spa_roots/show.slim
= javascript_pack_tag 'root_application'
```
この時読み込んでいる`root_application`には何が書かれているかというと、以下のとおりです。

```js:app/javascript/packs/root_application.jsx
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公式サイト](https://ja.reactjs.org/docs/react-dom.html#reference)によると、

```
ReactDOM.render(element, container[, callback])
```
> 渡された container の DOM に React 要素をレンダーし、コンポーネントへの参照（ステートレスコンポーネントの場合は null）を返します。

とのことですので、

`document.body.appendChild(document.createElement('div')` の部分で、bodyの末尾に作られた`<div>`タグに`<App />`をレンダーさせます。

- 参照：[ReactDOM#リファレンス](https://ja.reactjs.org/docs/react-dom.html#reference)

## Routerの導入

では、先程記載したこの`<APP />`には何が書かれているのかというと、以下の内容です。

```js:app/javascript/App.jsx
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`をインストールしました。

```sh
yarn add react-router-dom
```
そして、`app/javascript/App.jsx`では`BrowserRouter`(Routerという名前で使用)と`Routes`というライブラリを読み込んでいます。

それぞれのライブラリに関して深入りはしませんが、

- `BrowserRouter` ... ルーティングの切り替え機能を提供
- `Routes` ... それぞれのルーティングを提供

しているようです。

- 参考：[【React】ルーティング設定方法](https://qiita.com/k-penguin-sato/items/e46725edba00013a8300)

## 試しにページを表示

そして、この`<Routes />`には次のように書いてありました。`<Switch />`を使って、リクエストごとに表示する内容を切り替えていきます。

- APIドキュメント：[Switch](https://reactrouter.com/web/api/Switch)

```js:app/javascript/Routes.jsx
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`コンポーネントは表示されます。

[![Image from Gyazo](https://i.gyazo.com/6901cc6476b8af8bcf75651893550c38.png)](https://gyazo.com/6901cc6476b8af8bcf75651893550c38)

この、Fooコンポーネント内で、さらにBarコンポーネントを呼び出せば、完成です。

```js:app/javascript/pages/Foo.jsx
import React from 'react'
import { Bar } from '~/components/Bar'

export const Foo = () => {
  return (
    <div>
      <h1>これはfooページです。</h1>
      <Bar />
    </div>
  )
}
```

## 完成！
以上で、RailsにReactを途中から導入する設定が完了です。
いやー、、、先輩いなかったら完全に彷徨っていたと思います。。。

まだまだJSはわからないことが多いので、これからも地道に調べながら理解を深めていきたいです。