Rails6.1 でサポートされるActionView::Component を見てみる

こんばんは bosyu 開発メンバーの @lulu-ulul です。
bosyu Advent Calendar 2019 の 23日目の記事になります!
(GMTだとまだ23日なのでセーフですね!)

ActionView::Component

ActionView::Component gem が Rails 6.1 から正式にサポートされる様です。
今までRails標準だと parial にコンポーネントを切り出す事で共通化したり肥大化を防止したりしていましたが、このgemを導入すると View Component として切り出して扱う事ができる様になります。

類似の gem だと trailbrazer の cells とかがありますねー。

Introduction を読んで見る

#36388  を見ると ViewModel の中に template がヒアドキュメントで突っ込まれてる感じですね。

サンプルコード

class TestComponent < ActionView::Component
  validates :content, :title, presence: true

  def initialize(title:)
    @title = title
  end

  def self.template
    <<~'erb'
    <span title="<%= title %>"><%= content %></span>
    erb
  end

  private

  attr_reader :title
end

# We can render it in a view as:

<%= render(TestComponent.new(title: "my title")) do %>
  Hello, World!
<% end %>

# Which returns:

<span title="my title">Hello, World!</span> 


partial に比べて以下の利点がある様です。

コンポーネントのRubyClass単位でテスト可能になる
Rails ではViewテストは結合orシステムテストが推奨されていますが、それだとコストが重い&切り出したコンポーネントをそれぞれの呼び出し箇所でテストするのでDRYではなくなってしまいます。

CodeCoverageも取れる様になる
大半のツールはviewの計測に対応していないがActionView::Component にする事で取れる様になる。
現在は SimpleCov での計測のみに成功している様で、まだまだ開発中みたいです。

データフローが分かりやすくなる
partial だとどんな値を受け取ると期待しているかが宣言的ではないですが、initialize メソッドにより宣言的に書けるので自明になります。

(言われてみると大きめの partial だとパッと見で分からないから render してみて足りない locale 探したりする事ありましたね。partial 内に分岐があるとそれだと拾いきれない場合もありますし。)

また ActiveModel Validation をサポートしているため入力値の検証ができますし、コンポーネント固有のロジックを private method に記述することもできます。

標準化の対象にできる
Viewだと標準化してコード品質を保つのが難しいですが、RubyClassになるので、メソッド長や複雑性等他と同様の規約を導入しRubocop等で静的解析できる様になります。

ネスト呼び出しの場合Partial より数倍速い
partialファイルの内容にもよると思いますが、10階層の呼び出しを行う場合5倍以上のパフォーマンス向上がある様です。
partial 呼び出しが多層ネストするとパフォーマンスが割と無視できないレベルで悪化するという問題があったので、その点は解消されそうです。
これにより性能劣化をある程度気にしないで部品単位で切り出しやすくなりそうですねー。

template が ERB をヒアドキュメントでベタ書きはつらくない?

ERB をヒアドキュメントでベタ書きするはちょっと厳しくない?というのが最初に話題になった頃に持った感想でした。
6.1 からサポート予定ということで現状どうなってるのか気になって gem の github ページ見に行きました。現在はテンプレートは同改装に別ファイルとして持つ様になっています。やったー!

bin/rails generate component Example title content
                 invoke test_unit
                 create test/components/example_component_test.rb
                 create app/components/example_component.rb
                 create app/components/example_component.html.erb
What happened to inline templates?
Inline templates have been removed (for now) due to concerns raised by @soutaro regarding compatibility with the type systems being developed for Ruby 3.

なるほど、型チェックのための変更でもあるんですねー。

HamlやSlimも対応

ERBだけではなくHamlやSlimのテンプレートエンジンにも対応していますのでお好みの書式で書けます。

Previewing Components


Preview用の設定ファイルを準備しておくことで、プレビュー画面を簡単に追加できます。値の確認や簡易コンポーネントカタログとして利用できそうですねー。(BEMベースだとスタイル当たらないので厳しそうですが。)

# test/components/previews/test_component_preview.rb

class TestComponentPreview < ActionView::Component::Preview
  # http://localhost:3000/rails/components/test_component/with_default_title でアクセスできる
  def with_default_title
    render(TestComponent, title: "Test component default")
  end

  # http://localhost:3000/rails/components/test_component/with_long_title でアクセスできる
  def with_long_title
    render(TestComponent, title: "This is a really long title to see how the component renders this")
  end
end


余談
#36388 を見ると render メソッドが option に渡されたオブジェクトが render_in メソッドを持っているかで判別して分岐する様になってますねー。
ActionView::Component じゃなくてもrender_in メソッドの生えたオブジェクト渡せば render メソッドから引数受け取ってよしなにできそうですね(使い途は謎)。

まとめ

・Rails6.1 から ActionView::Component がサポートされるよ
・テンプレートはインラインから別ファイル切り出しに変わってたよ(とても うれしい)
・ネストした時に partial よりパフォーマンス良いよ
・haml とかも対応してるよ

template が別ファイルになって抵抗感なくなったので、partial から置き換え有りな気がしますねー。


この記事が気に入ったらサポートをしてみませんか?