RailsのViewComponentを触ってみた
RailsのAction ViewでReactライクにHTMLコーディングができるViewComponentを触ってみた。
CSSをTailwindCSSで書く場合はコンポーネント機能は必須だ。従来のERBのパーシャル機能だけではつらくなってくるはず。
ViewComponentはRailsのデフォルト機能ではないので、Gemに追加する。
docker compose run --rm web bundle add view_component
ひとまずグローバルヘッダをマニュアルに沿ってコンポーネント化してみた。以下のコマンドからコンポーネントをジェネレートできる。
docker compose run --rm web rails g component GlobalHeader domain current_user
実行すると、以下の2つのファイルが生成された。
- global_header_component.rb
- global_header_component.html.erb
rbファイルがロジック部分で、erbファイルがテンプレートか。Vue.jsっぽい?
以下書いてみたサンプル。
global_header_component.rb
# frozen_string_literal: true
class GlobalHeaderComponent < ViewComponent::Base
def initialize(domain:, current_user:nil)
@domain = domain
@current_user = current_user
case domain
when "staff"
@bg = "bg-staff"
when "customer"
@bg = "bg-customer"
else
@bg = "bg-admin"
end
end
end
global_header_component.html.erb
<header class="p-8 flex justify-between items-center <%= @bg %>">
<span class="font-bold text-xl">顧客管理システム</span>
<div>
<%=
if @current_user
link_to "ログアウト", "#{@domain}_session".to_s, method: :delete
else
link_to "ログイン", "#{@domain}_login".to_s
end
%>
</div>
</header>
rbファイルのCSSクラス名処理の箇所が面倒な書き方になっているが、CSS名をスタティックに書いておかないとTailwindCSSのコンパイラがクラスを認識しないためだ。
rbファイルにロジックを書いて、erbファイルにデータを渡す、というのが基本っぽいがフォームの場合はどうするのだろう?
ジェネレートコマンドを使わないで、rbファイルを手動で作ってもOKだった。erbがいらないケースもある。
テキストフィールドを表示するコンポーネント。
text_field_component.rb
class TextFieldComponent < ViewComponent::Base
def initialize(form:, field:, label:, type: 'text', additional_class: "")
@form = form
@field = field
@label = label
@type = type
@additional_class = additional_class
end
def call
content = @form.label @field, @label, class: "block text-sm font-medium leading-6 text-gray-900"
content += tag.div class: "mt-2" do
@form.text_field @field, type: @type,
class: "block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm
ring-1 ring-inset ring-gray-300 placeholder:text-gray-400
focus:ring-2 focus:ring-inset focus:ring-indigo-600
sm:text-sm sm:leading-6 #{@additional_class}"
end
content
end
end
TailwindCSSの大量のクラス名がコンポーネント内に押し込める。便利だ。
ReactでいうところのChildrenのような振る舞いも可能だった。
class AuthFormComponent < ViewComponent::Base
def initialize()
end
def call
content_tag :div, class: "flex min-h-full flex-col justify-center px-6 py-12" do
concat content_tag(:div, content, class: "mx-auto w-full max-w-sm")
end
end
end
content
というのがchildren
に該当するっぽい。
ディレクトリ構造どうするのか等々は検討が必要だが、軽くReactぽくも実装できるのでかなり良い。ERBのパーシャルはもう必要ないと思う。
Discussion