はじめに
モノレポ(Monorepo)は、「Monolithic Repository」の略で、複数のプロジェクトやコードベースを1つのリポジトリで管理する方法を指す。
通常は、1つのリポジトリに対して1つのプロジェクト(リポジトリ単位での管理)を行う「マルチレポ(Multi-repo)」とは対照的な手法。
モノレポでは、以下のような複数のアプリケーションやライブラリを1つのリポジトリで管理する
- バックエンド(例: Rails)
- フロントエンド(例: React、Vue.js)
- 共通のユーティリティ(例: APIクライアント、ヘルパー関数)
- サードパーティツールの統合
モノレポ構成を利用すれば、Railsをバックエンド、Reactをフロントエンドとして統合した効率的なプロジェクトを構築できる。以下では、RailsからReactにデータを渡し、動的なUIを構築する方法を具体的なコード例を用いて説明する。
モノレポ構成の概要
例えばRailsアプリケーションはapp/
ディレクトリから始まり、フロントエンドはfrontend/packages
以下で管理する。この構成では、Reactアプリケーションを@
プレフィックスでモジュール化し、再利用性と管理性を向上させている。
ディレクトリ構成
my-monorepo/
├── app/ # Railsアプリケーション
│ ├── controllers/ # コントローラー
│ ├── models/ # モデル
│ ├── views/ # ビュー
│ │ ├── layouts/
│ │ │ └── react.html.erb # React用共通レイアウト
│ │ └── tasks/
│ │ └── show.html.erb # RailsからReactにデータを渡すテンプレート
├── frontend/ # フロントエンド(Reactなど)
│ ├── packages/ # フロントエンドのパッケージ
│ │ ├── @app/ # Reactアプリケーション
│ │ │ ├── src/
│ │ │ │ ├── index.tsx # Reactエントリーポイント
│ │ │ │ ├── App.tsx # Reactメインコンポーネント
│ │ │ │ ├── components/ # UIコンポーネント
│ │ │ │ │ └── TaskList.tsx # タスクリストコンポーネント
│ │ │ └── package.json # パッケージ設定
│ │ ├── @ui/ # 共通UIコンポーネント
│ │ ├── @helpers/ # ヘルパー関数
├── Gemfile # Railsの依存ライブラリ
├── package.json # モノレポ全体のパッケージ設定
RailsからReactへのデータの流れ
RailsからReactにデータを渡し、Reactがそのデータを使って動的に描画する仕組みを記載
Railsでデータを準備
RailsのコントローラーでReactに渡すデータを準備する。
app/controllers/tasks_controller.rb
class TasksController < ApplicationController
def show
@tasks = Task.where(user_id: current_user.id)
render layout: 'react'
end
end
Railsのビューでデータを埋め込む
react_data
ヘルパーを使い、Reactに渡す初期データをJSON形式で埋め込む。
app/views/tasks/show.html.erb
<%= react_data({
today: Date.current.strftime('%Y-%m-%d'),
tasks: @tasks.map do |task|
{
id: task.id,
name: task.name,
done: task.done,
due_date: task.due_date.strftime('%Y-%m-%d')
}
end
}) %>
このコードがHTMLにレンダリングされると、以下のようなスクリプトタグが生成される
<script>
タグ:
class="react-data"
: Reactデータを特定するためのクラス属性。data
属性: JSON形式でRailsから渡されたデータを格納。文字列形式のJSONとして埋め込まれている
<script class="react-data" data='{
"today": "2025-01-13",
"tasks": [
{
"id": 1,
"name": "Task A",
"done": false,
"due_date": "2025-01-15"
},
{
"id": 2,
"name": "Task B",
"done": true,
"due_date": "2025-01-20"
}
]
}'></script>
Railsで埋め込まれたデータを取得するユーティリティ
Railsで生成されたHTMLには、react_data
ヘルパーによってデータが埋め込まれている。このデータをReactで取得するために、ユーティリティ関数getReactData
を用意する。
frontend/packages/@app/src/utils/getReactData.ts
/**
* Railsから渡されたデータを取得する関数
* @param selector データが埋め込まれた要素のセレクタ(デフォルト: `.react-data`)
* @param dataAttr データが格納されている属性名(デフォルト: `data`)
*/
const getReactData = (selector: string = '.react-data', dataAttr: string = 'data') => {
const nodes = document.querySelectorAll(selector);
return Array.from(nodes).reduce((mergedData, node) => {
const data = node.getAttribute(dataAttr);
return { ...mergedData, ...(data ? JSON.parse(data) : {}) };
}, {});
};
export default getReactData;
作成したgetReactData関数を使うことで、Railsから送られてきたJSON形式のデータを、React側でJavaScriptのオブジェクトとして利用できる形に変換している。
const initialData = getReactData();
console.log(initialData);
// 出力:
// {
// today: "2025-01-13",
// tasks: [
// { id: 1, name: "Task A", done: false, due_date: "2025-01-15" },
// { id: 2, name: "Task B", done: true, due_date: "2025-01-20" }
// ]
// }
JSON形式 | JavaScriptオブジェクト |
データ交換用のテキスト形式 | 実行可能なJavaScriptオブジェクト |
キーは必ずダブルクォートで囲む | キーはダブルクォート不要(文字列でも可) |
例: {"key": "value"} | 例: { key: "value" } |
Railsで生成されたデータをReactアプリケーションに渡す
Reactアプリケーションのエントリーポイントで、getReactData
を使ってデータを取得し、親コンポーネントに渡す。
frontend/packages/@app/src/index.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import getReactData from './utils/getReactData';
import App from './App';
// Railsから渡されたデータを取得
const initialData = getReactData();
const appElement = document.getElementById('app');
if (appElement) {
createRoot(appElement).render(
<StrictMode>
<App {...initialData} />
</StrictMode>
);
}
Reactの親コンポーネントでデータを受け取る
親コンポーネントApp
は、Railsから渡されたデータをprops
として受け取り、必要に応じて子コンポーネントに渡す。
frontend/packages/@app/src/App.tsx
import React from 'react';
import TaskList from './components/TaskList';
interface Task {
id: number;
name: string;
done: boolean;
due_date: string;
}
interface AppProps {
today: string;
tasks: Task[];
}
const App: React.FC<AppProps> = ({ today, tasks }) => (
<div>
<h1>タスク一覧</h1>
<p>今日の日付: {today}</p>
<TaskList tasks={tasks} />
</div>
);
export default App;
Reactの子コンポーネントでデータを描画する
親コンポーネントから渡されたデータを使い、タスクリストを描画する子コンポーネントを作成する。
frontend/packages/@app/src/components/TaskList.tsx
import React from 'react';
interface Task {
id: number;
name: string;
done: boolean;
due_date: string;
}
interface TaskListProps {
tasks: Task[];
}
const TaskList: React.FC<TaskListProps> = ({ tasks }) => (
<ul>
{tasks.map((task) => (
<li key={task.id}>
<label>
<input type="checkbox" checked={task.done} readOnly />
{task.name}(締切: {task.due_date})
</label>
</li>
))}
</ul>
);
export default TaskList;
まとめ
- Railsでデータを埋め込む
react_data
ヘルパーでRailsのデータをJSON形式でHTMLに埋め込む。
- Reactエントリーポイントでデータを取得
getReactData
を使って埋め込まれたデータを取得し、<App />
に渡す。
- 親コンポーネントから子コンポーネントにデータを渡す
- 親コンポーネントがデータを受け取り、子コンポーネントに
props
として伝播。
- 親コンポーネントがデータを受け取り、子コンポーネントに
- Reactで動的なUIを構築
- 取得したデータを基に、Reactが動的にUIをレンダリング。
RailsとReactを組み合わせることで、動的でインタラクティブなフロントエンドを実現可能。