[Rails] view_componentというgemがすごく便利なので紹介します

2022/09/24

Rails

突然ですが、みなさんはRailsのviewを活用できていますか?

RailsはもはやAPIとしてしか使っていないとか、viewはあることはあるんだけど、実情はReactが動いているからviewとしてレンダリングする機能は不要とか、そんな方が多いんじゃないでしょうか。

その原因として、Reactが便利すぎるというのはもちろんなのですが、その思想にあるコンポーネントごとの責務を明確にする、というところがRailsのviewには特に欠けている点かなと思います。

というのも、昨今の開発では画面を役割ごとに分割し、それぞれの振る舞いを個別に定義するコンポーネントという概念を使用した開発がメインになっているのですが、デフォルトのrailsでその機能を使用するにはpartialを使用せざるを得ず、それぞれのpartialにわたす引数が複雑になってしまったり、partial内の値をview内で再定義する必要があるため、各々のpartialが何を渡してどのような振る舞いをするのかをコードをパッと見て把握するのが難しいという課題がありました。

今回はそんなRailsのview環境における救世主ともなりうるgemである view_component のご紹介です。

概要

https://github.com/ViewComponent/view_component

view_componentのgemはGitHub内のエンジニアが作成しています。

2022/10時点で直近のリリースが7日間以内ということで、まだまだ安心して使っていけそうですね。

使い方

ここからは、こちらの公式ドキュメントをベースに、それ以外のケースでも自分がよく使っている記述を複数紹介します。

コンポーネントをデータごとにレンダリングする場合

https://viewcomponent.org/guide/collections.html

sampleでは、postが複数データあり、それごとにレンダリングするケースです。

app/components/sample/component.rb
class Sample::Component < ViewComponent::Base
  with_collection_parameter :post

  def initialize(post: Post.new, clickable: true)
    @post = post
    @clickable = clickable
  end
end
app/views/samples/index.html.haml
= render Sample::Component.with_collection(@posts, clickable: true)

コンポーネント内でApplicationHelperに記載したメソッドを使用する場合

以下のようにApplicationHelperをincludeする必要があります

app/components/sample/component.rb
class Sample::Component < ViewComponent::Base
  include ApplicationHelper

  with_collection_parameter :post

  def initialize(post: Post.new, clickable: true)
    @post = post
    @clickable = clickable
  end
end

コンポーネント内でturbo_frame_tagを呼び出す場合

以下のように Turbo::FramesHelperをincludeする必要があります

app/components/sample/component.rb
class Sample::Component < ViewComponent::Base
  include Turbo::FramesHelper

  with_collection_parameter :post

  def initialize(post: Post.new, clickable: true)
    @post = post
    @clickable = clickable
  end
end

コンポーネントごとのテストを書く場合

https://viewcomponent.org/guide/testing.html

spec/rails_helper.rb
require "view_component/test_helpers"
require "capybara/rspec"

RSpec.configure do |config|
  config.include ViewComponent::TestHelpers, type: :component
  config.include Capybara::RSpecMatchers, type: :component
end
spec/components/sample_component_spec.rb
require "rails_helper"

RSpec.describe SampleComponent, type: :component do
  subject do
    render_inline(described_class.new(title: "my title")) { "Hello, World!" }
  end

  it "renders component" do
    subject

    expect(page).to have_css "span[title='my title']", text: "Hello, World!"
    # or, to just assert against the text
    expect(page).to have_text "Hello, World!"
  end
end

コンポーネントでもtailwindcss-railsを使用する場合

config/tailwind.config.js
module.exports = {
  content: [
    ...
    './app/components/**/*',
  ],
  ...
}

その他使用する際のポイント

こちらでは、使用している際に個人的に注意しているポイントを記述します

データが何であっても表示できる状態にする

例えば、titleとdescriptionを持つPostモデルを引数に渡されるcomponent、PostCardコンポーネントの場合、以下のように記載しています。

コンポーネントはデータに不足があったケースでも表示できるようにしておくことで、テストの際も安心して記載できると考えてのことです。

app/components/sample/component.rb
class Sample::Component < ViewComponent::Base
  with_collection_parameter :post

  def initialize(post: Post.new, clickable: true)
    @post = post
    @clickable = clickable
  end

  def title
    @post.title || 'No Title'
  end

  def description
    @post.description || 'No Description'
  end
end
app/components/sample/component.html.haml
.p-4
  %p.text-gray-700.text-base.lg:text-lg.font-semibold.mb-1
    = title
  %p.text-gray-700.text-sm.lg:text-base
    = description

悩んでいるところ

コンポーネントごとの命名の難しさ

現状はまだまだ導入したしたてという事もあり、AtomicDesignなどを考慮せず雑に作って居ますがそのうちcomponents配下がカオスになりそうな気がしています。

この辺りの考慮を今後はしていきたいなと思っています。

感覚的には、モデルなどをベースに考慮していくのが良さそうな気がしていますが…

Related Posts

devContainerでDockerかつforemanなrailsプロジェクトをdebugする

devContainerでDockerかつforemanなrailsプロジェクトをdebugする

[Hotwire] turbo_frame_tagで非同期いいねボタンを実装する

[Hotwire] turbo_frame_tagで非同期いいねボタンを実装する