Introduce support for 3rd-party component frameworks by joelhawksley · Pull Request #36388 · rails/rails
Note: This PR initially was titled: Introduce support for ActionView::Component. I've updated the content to better reflect the changes we ended up shipping, to use the new name of GitHub's...
そのコンポーネントフレームワークの中でもGitHubが作った
ViewComponent(view_component) というものが存在するので、
今回はこのViewComponentについての解説を簡単にしていきたいと思います。(類似ライブラリについては後述します)
github/view_component
A framework for building reusable, testable & encapsulated view components in Ruby on Rails. - github/view_component
少し補足しておくと、「ActionView::ViewComponent」がRails6.1に取り込まれるというニュアンスで、試してみた記事などがありますが、2020年11月08日現在の状況としては少し異なるように思われるので、Mergeされただけで、正式にリリースされていない情報の場合は、過去の情報はあまり信頼しないで、自分で最新の動向をなるべく仕入れるようにしていきましょう。この記事を更新した後も変わるかもしれませんし。
また、下記PRにもあるようにActionView::ViewComponentはViewComponentにRenameされた模様です。
Renaming to ViewComponent, Syntax Changes · Issue #206 · github/view_component
👋 Hi there folks! After some conversations with the Rails team, we’ve decided to make the following changes to ActionView::Component: Rename the gem to ViewComponent Revert to the original render(M...
ViewComponentの概念
React.Component – React
A JavaScript library for building user interfaces
GitHubが作ったモチベーション
またこのライブラリを作ったのはGitHubなのですが、GitHubではRailsのViewテンプレートをふんだんに使われており、Viewレイヤーでのテストに問題を抱えていたようです。
これまではシステムテストつまりはブラウザを使ってのE2Eテストないし、コントローラーテストを行う必要があるのですがとても時間がかかります。
ViewComponentではその部分を解決するのとViewデータの流れをわかりやすくするのがメインモチベーションのようです。
簡単な使い方
利用している環境は下記の通り。
---
- Rails 6.0.x
- Ruby 2.7.1
Rails6.1以降、ViewComponent内部で実装されているモンキーパッチがなくなるという感じですかね。
Gemのインストール
gem "view_component", require: "view_component/engine"
RSpecへの対応(Option
Rspecでテストを書きたい人は下記内容をspec/rails_helper.rbに記載しましょう。
require "view_component/test_helpers" RSpec.configure do |config| config.include ViewComponent::TestHelpers, type: :component end
コンポーネントを実際に作る
今回はブログの一覧ページにあるリストをコンポーネントにしようと思います。
bundle exec rails g component Blog blog create app/components/blog_component.rb invoke rspec create spec/components/blog_component_spec.rb invoke erb create app/components/blog_component.html.erb
ざっくりコンポーネントを実装する
1. 先にBlogComponentの中身を実装しておきます。
// app/components/blog_component.rb class BlogComponent < ViewComponent::Base def initialize(blog:) @blog = blog end def link link_to blog.title, blog_path(uuid: blog.uuid) end def publish_date if blog.first_published_at.present? blog.first_published_at.strftime("%Y/%m/%d") else "未公開記事" end end private attr_reader :blog end
テンプレートはこんな感じ。
// app/components/blog_component.html.erb <li> <div> <h2> <%= link %> </h2> <span> <%= publish_date %> </span> <span> <% blog.tags.each do |tag| %> <%= link_to tag.name, tag_path(tag.name), class: "blog-tag" %> <% end %> </span> </div> </li>
上記のようにコンポーネントを作っておくことで、
テンプレート内部の条件分岐が少なくなり、これまでDecorator層やHelperに書いていた実装も、コンポーネントに書いておけそうで、見通しが良くなるかと思います。(今回のサンプルコードでは少ないのであまり恩恵はないような気がしますが。)
2. 下記のように、ブログ一覧画面で、BlogComponentの呼び出しを下記のように行います。
<div class="content"> <ul class="blog_list"> <% @blogs.each do |blog| %> <%= render(BlogComponent.new(blog: blog)) %> <% end %> </ul> </div>
Rspecでテストを書いてみる
筆者はRspec派なので、Rspecで簡単なテストを書くとどういう感じになるのかを書いておきます。
require "rails_helper" RSpec.describe BlogComponent, type: :component do it "Blogのタイトルが入っていること" do blog = Blog.new(title: "test", first_published_at: Time.now, uuid: SecureRandom.uuid) expect(render_inline(described_class.new(blog: blog)).css("h2 a").to_html).to include(blog.title) end it 'ブログの公開日が設定されている場合は「%Y/%m/%d」のフォーマットで表示される' do blog = Blog.new(title: "test", first_published_at: Time.now, uuid: SecureRandom.uuid) expect(render_inline(described_class.new(blog: blog)) .css("span.publish_date").to_html).to include(blog.first_published_at.strftime("%Y/%m/%d")) end it "ブログの公開日が設定されていない場合、「未公開記事」と表示される" do blog = Blog.new(title: "test", first_published_at: nil, uuid: SecureRandom.uuid) expect(render_inline(described_class.new(blog: blog)).css("span.publish_date").to_html).to include("未公開記事") end end
render_inlineというヘルパーメソッドがComponentSpecに対して生えており、
これはコンポーネントをRenderしてNokogiri::HTML::DocumentFragmentを返すようなメソッドです。
なので.cssメソッドでCSSセレクターでテストを行える...という具合になっているようです。
ひとまずこのようにテストできれば、コントローラーSpecやE2Eのテストよりも高速にテストできるかと思います。
より詳しいことについて
あんまり長くなりすぎても伝えたいことが伝わらない気がするので、今回は概要の紹介にとどめておきます。詳しいことは下記の公式ドキュメントを見るといいと思います。ところでこのドメインはすごい勘違いを生みそうな予感です...
view_component
A framework for building reusable, testable & encapsulated view components in Ruby on Rails.
正直、紹介した機能でも必要十分に機能がそろってて、あまり高機能にするとかえって複雑さを増すと思うと個人的には思うんですが、Vueでもある「slot」(実験的ですが)もあるみたいなので、皆様も是非是非使ってみてください。
その他質問や思うところなどなど。
類似ライブラリ
trailblazer/cells
View components for Ruby and Rails. Contribute to trailblazer/cells development by creating an account on GitHub.
あまり違いを分かっていないのですが、基本的には同じ思想でやろうとしていることは同じで、正直好みで選んでいいと思います。(個人的にはViewComponentのほうがカジュアルに使えそうな感じはする。)
下記は、trailblazer/cellsについて解説している記事です。
Railsアプリに秩序をもたらすGem、Trailblazerとは? - Qiita
ここ1年で、Trailblazerの認知度は急速に上がってますよね。 BestGemsで見てみるとここ1年でめっちゃ伸びてます。 1月時点で6万4千DLだったのが、9月現在で既に15万を突破しているので、今年だけで倍以上のダウンロー...
またこのほかに
dry-rb/dry-viewや、
dry-rb/dry-view
Complete, standalone view rendering system that gives you everything you need to write well-factored view code. - dry-rb/dry-view
Komponent というものも存在するようです。
komposable/komponent
An opinionated way of organizing front-end code in Ruby on Rails, based on components - komposable/komponent
どういう場合にコンポーネントフレームワークを採用するか?
- RailsのViewテンプレートやRailsのHelperがカオスで、そのあたりにテストを書かないとやっていられない!!!という場合に採用するといいでしょう。
- RailsのHelperやDecorator層もよほどViewComponentレイヤーが複雑にならない限りはViewComponet内にメソッドとして書いてしまっていいので、不要になるかもしれないと僕は考えています。
- 現状のPartialTemplateからComponentに置き換えていくだけでもテストはしやすくなると思うので、RailsのViewをふんだんに使っているプロジェクトは、置き換えも段階にできて簡単なはずなので、「とりあえず置き換えてみる」ということをしてみてもいいのかなぁと思いました。
- Rails5.0以降、Ruby2.4以降なら導入できるはずなので、結構簡単に導入できると思います。
- 現状VueやReactを使っている場合は、フロントエンドのレイヤーでComponentへのテストは書けるし、置き換えるメリットは全くないので無視しましょう。
どの粒度でコンポーネントに分ければいいですか?
会社や作っているプロダクトによって変わるというのが答えになると思います。
フロントエンドと異なり、イベントやデータの伝搬(親 -> 子はあるが、子 -> 親はないはず)が大変複雑になることはまぁ起こることはないはずなので、フロントエンドのコンポーネント設計に比べると簡単な気がします。答えはないですが、ReactやVueの記事にこういった話題の投稿があるはずなので、参考になると思います。