突然ですが、みなさんは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が複数データあり、それごとにレンダリングするケースです。
class Sample::Component < ViewComponent::Base
with_collection_parameter :post
def initialize(post: Post.new, clickable: true)
@post = post
@clickable = clickable
end
end
= render Sample::Component.with_collection(@posts, clickable: true)
コンポーネント内でApplicationHelperに記載したメソッドを使用する場合
以下のようにApplicationHelperをincludeする必要があります
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する必要があります
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
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
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を使用する場合
module.exports = {
content: [
...
'./app/components/**/*',
],
...
}
その他使用する際のポイント
こちらでは、使用している際に個人的に注意しているポイントを記述します
データが何であっても表示できる状態にする
例えば、titleとdescriptionを持つPostモデルを引数に渡されるcomponent、PostCardコンポーネントの場合、以下のように記載しています。
コンポーネントはデータに不足があったケースでも表示できるようにしておくことで、テストの際も安心して記載できると考えてのことです。
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
.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配下がカオスになりそうな気がしています。
この辺りの考慮を今後はしていきたいなと思っています。
感覚的には、モデルなどをベースに考慮していくのが良さそうな気がしていますが…