👻

RailsのViewComponentを触ってみた

2024/04/30に公開

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