SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Railsによるクライアントサイド開発入門

Rails 7ではReactアプリ作成が簡単に! importmap-railsとPropshaftを活用したチュートリアルで体感しよう

Railsによるクライアントサイド開発入門 第2回

  • X ポスト
  • このエントリーをはてなブックマークに追加

Rails 7ではより容易に! Reactアプリを作成してみよう!

 本記事では、RailsアプリにReactを組み込む中で、フロントエンド開発の基本的な流れをおさえていきます。

 Reactは、UI構築に適したコンポーネント指向のJavaScriptフレームワークです。Rails 7が登場するまでは、ReactやVueといったJavaScriptフレームワークはバンドラーでアプリケーションに含めるのが普通でした。Rails 7では、importmap-railsによってReactモジュールを直接インポートすることが容易になっていますので、ここではこの方法によってReactを導入します。

 作成するReactアプリはシンプルなものです。コントローラとアクションを1個作成し、そのビューのコンテンツをReactによって生成します。ただし、今回はReactによる開発手法の紹介が目的ではありませんので、コンテンツは最低限(メッセージと背景画像が表示されるのみ)にとどめています。

土台のアプリケーションを作成する

 まずは、土台となるRailsアプリケーションを作成します。作成するアプリケーションのライブラリ構成は以下のとおりです(行頭の[○]はデフォルト構成のまま使うもの、[-]はデフォルト構成から外すもの、[+]は加えるもの)。

  • [○]importmap-rails
  • ]Sprockets(sprockets、sprockets-rails)
  • ]Propshaft(propshaft)
  • ]Hotwire(turbo-rails、stimulus-rails)

 アプリケーションは、rails newコマンドで作成します。今回は、アセットパイプラインにSprocketsに替えてRails 7から使用できるようになったPropshaftを使ってみることにします。また、Reactに集中するために同じくJavaScriptのライブラリであるHotwireのインストールをスキップすることにします。

 アプリケーションの名前はreact_appとします。デフォルトのSprocketsに代わりPropshaftを使いますので、前節で紹介した-aオプションでpropshaftを指定しています。さらにHotwireのインストールをスキップしますので、--skip-hotwireオプションも指定しています。

 アプリケーションが作成できたら、続く作業のためにcdコマンドでアプリケーションのフォルダに移動しておきます。

% rails new react_app -a propshaft --skip-hotwire
…略…
% cd react_app

 この段階で、フロントエンド関連のファイルがいくつか生成されています。後ほどこれらに手を入れていきますが、どのようなフォルダやファイルがあるかここで見ておくことにしましょう(フロントエンド関連でないフォルダ、ファイルは省略しています)。

react_app
├── app
│   ├── assets … アセットファイルを置くフォルダ
│   │   ├── images … 画像ファイルを置くフォルダ
│   │   └── stylesheets … CSSファイルを置くフォルダ
│   │       └── application.css… アプリケーション共通のスタイルシート
│   └── javascript … JavaScriptファイルを置くフォルダ
│        └── application.js … アプリケーション共通のJavaScriptエントリポイント
├── config
│   └── importmap.rb … importmap-railsの設定ファイル
├── public … アプリケーションの公開フォルダ
│   └── assets … ビルドされたアセットが置かれるフォルダ
└── vendor
    └── javascript … ダウンロードされたJavaScriptファイルが置かれるフォルダ

 続けてrails generate controllerコマンドで、コントローラをアクションも指定して作成します。コントローラ名はreact、アクション名はhelloとします。

% rails generate controller react hello
…略…

 これで、コントローラapp/controllers/react_controller.js、ビューapp/views/react/hello.html.erbが作成されます。ルートページを、作成したreact#helloアクションに変更しておきましょう。

リスト config/routes.rb
Rails.application.routes.draw do
  get 'react/hello'
  # ルートページをreact#helloアクションに
  root "react#hello"
end

 ここでrails serverコマンドでPumaサーバを起動し、「http://localhost:3000/」にWebブラウザでアクセスして、react#helloが呼び出されるのを確認しておきます。これで土台の準備ができました。

importmap-railsを設定する

 Reactは、そのモジュールを直接インポートします。具体的には、importmap-railsに対してモジュールを「ピン留め」していきます。ピン留めとは、論理的なモジュール名をその実体(ローカルのJavaScriptファイルあるいはnpmパッケージなど)に関連付けることをいいます。ピン留めによって、インポートするモジュールを場所やバージョンに依存しない論理的な名前で指定できます。bin/importmapコマンドを使って、以下のようにピン留めします。

% bin/importmap pin react react-dom/client
Pinning "react" to https://ga.jspm.io/npm:react@18.0.0/index.js
Pinning "react-dom/client", to: https://ga.jspm.io/npm:react-dom@18.1.0/client.js
Pinning "process" to https://ga.jspm.io/npm:@jspm/core@2.0.0-beta.24/nodelibs/browser/process-production.js
Pinning "scheduler" to https://ga.jspm.io/npm:scheduler@0.21.0/index.js

 今回使うReactのモジュールはreactとreact-dom/clientなので、これらを指定してbin/importmapコマンドを実行すると、自動的にパッケージの場所を割り出し、依存するパッケージも含めてモジュールをピン留めしてくれます。

 ピン留めした結果は、importmap-railsの設定ファイルであるconfig/importmap.rbに反映されます。上記のbin/importmapコマンド実行後のconfig/importmap.rbは以下のようになっています。

リスト config/importmap.rb
pin "application", preload: true	(1)
# 以下が新たに追加された部分
pin "react", to: "https://ga.jspm.io/npm:react@18.0.0/index.js"	(2)
pin "react-dom/client", to: "https://ga.jspm.io/npm:react-dom@18.1.0/client.js"
pin "process", to: "https://ga.jspm.io/npm:@jspm/core@2.0.0-beta.24/nodelibs/browser/process-production.js"
pin "scheduler", to: "https://ga.jspm.io/npm:scheduler@0.21.0/index.js"

 これらは全て、ピン留めです。(1)は、デフォルトで用意されているモジュールのピン留め、(2)以降はReact関連モジュールのピン留めです。ここで登場するpin構文は、モジュール名に:toオプションで指定されるモジュール実体をピン留めします(同名の場合は:toオプションは(1)のように省略可能)。:preloadオプションは、モジュールと依存関係にあるモジュールを先に読み込むときtrueに設定します。

 (2)では、モジュール"https://ga.jspm.io/npm:react@18.0.0/index.js"を"react"という名前にピン留めしてします。この設定により、モジュール名として"react"が指定されたときには、実体として"https://ga.jspm.io/npm:react@18.0.0/index.js"を使うというように対応付けられます。

 config/importmap.rbに記述したピン留めは、ビューからはjavascript_importmap_tagsヘルパーメソッドの呼び出しでscript要素などに展開されます。JavaScriptからはimport文で参照することができます。具体的にどうなっているかは、本記事の後半で紹介します。

[NOTE]JSPM

 bin/importmapコマンドは、既定でJSPMが管理するCDN(Content Delivery Network)からパッケージを検索し、モジュールのURLをピン留めします。JSPMは、Import Mapsのためのパッケージ管理とコンテンツ配布の機能を提供します。--fromオプションを指定することで、unpkgやjsdelivrといったCDNも選択できます。

% bin/importmap react --from jsdelivr	# CDNとしてjsdelivrを指定
Pinning "react" to https://cdn.jsdelivr.net/npm/react@18.0.0/index.js

 続けて、Reactのコンポーネントを作成していきますが、その前にコンポーネントファイルを入れるフォルダを作成し、そのフォルダもピン留めしておきます。この設定により、コンポーネントも置き場所を気にせずにモジュールとしてインポートすることが容易になります。app/javascriptにcomponentsフォルダを作成し、config/importmap.rbに以下の内容を追記します。

リスト config/importmap.rb
pin_all_from "app/javascript/components", under: "components"

 pin_all_from構文は、指定したフォルダ以下のJavaScriptファイルを全てピン留めする指定です。:underオプションを指定すると、モジュール名にその値が前置されます。この場合は、"components/xxxx"というようになります。ここでは省略していますが、必要に応じて前述した:preloadオプションを指定できます。

Reactコンポーネントを作成する

 Reactモジュールの直接インポートの準備ができましたので、Reactのコンポーネントを作成します。このコンポーネントは、ビュー中の指定するdiv要素に対して、「こんにちは○○さん!」というメッセージを設定するというシンプルなものです。コンポーネントのファイルは、app/javascript/componentsフォルダにreact_hello.jsとして作成します。先ほど設定したpin_all_from構文により、react_hello.jsは論理名"components/react_hello"でインポートできます。

リスト app/javascript/components/react_hello.js
import React from 'react'	(1)
import {createRoot} from 'react-dom/client'

const Hello = props => (	(2)
  React.createElement('div', null, `こんにちは ${props.name} さん!`)
)

Hello.defaultProps = {
  name: '名無し'
}

document.addEventListener('DOMContentLoaded', () => {
  const container = document.getElementById('app');
  const element = React.createElement(Hello, {name: 'やまうちなお'}, null);
  createRoot(container).render(element);	        (3)
})

 (1)は、コンポーネントで必要なモジュールをインポートしています。このように、ピン留めした論理的な名前でモジュールを指定できます。(2)はHTML(この場合はdiv要素)を生成して返す関数Helloを定義しています。(3)はそれを呼び出してid属性が'app'である要素に戻り値を設定しています。

 コンポーネントを読み込む指定は、app/javascipt/application.jsに記述します。このファイルは、アプリケーション共通のエントリポイントで、config/importmap.rbの先頭行にあるpin構文によってピン留めされています。そのため、ビュー中のjavascript_importmap_tagsヘルパーメソッドによってscript要素に展開されます。コンポーネントのモジュールは、pin_all_from構文で指定した「components」の付いた名前で指定できます。

リスト app/javascipt/application.js
import "components/react_hello"		# 追加

 そして、ビューにReactコンポーネントで更新されるdiv要素を追加しておきます。div要素のid属性の値であるappは、コンポーネント中で指定されていますから、両者は一致する必要があります。

リスト app/views/react/hello.html.erb
<h1>React#hello</h1>
<p>Find me in app/views/react/hello.html.erb</p>
<div id="app"></div>		# 追加

アセットを配置する

 最後に、アセットである背景画像を配置します。画像ファイルの置き場所であるapp/assets/imagesにpig1.pngを配置し、app/assets/stylesheets/application.cssに以下のようにスタイルを追記します。このファイルは、アプリケーション共通のCSSファイルであり、ビュー中のstylesheet_link_tagヘルパーメソッドによってlink要素に展開されます。

リスト app/assets/stylesheets/application.css
body {
    background-image: url('pig1.png');
}

 再びPumaサーバを起動し、図のようなページが表示されれば成功です。

図 完成したReactアプリケーション
図 完成したReactアプリケーション

[NOTE]JSXは使用できない

 Reactでは、JSX構文でJavaScriptコードの中にHTMLを直接記述することができますが、これにはBabelによるトランスパイルが必要です。importmap-railsを使う場合にはJSX構文は使用できませんので、サンプルではHTMLをJavaScriptコードによって生成しています。JSXを使う例については、jsbundling-railsを使ったバンドラーの回で紹介する予定です。

次のページ
アプリ内でimportmap-railsとPropshaftが果たす役割を見てみる

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Railsによるクライアントサイド開発入門連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 山内 直(WINGSプロジェクト ヤマウチ ナオ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook <個人紹介>WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/15973 2022/06/27 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング