先日の LT 会で ViewComponent の紹介をされていて面白そうだったので、試してみます。
ViewComponent は、React インスパイアの Rails 向けのビューコンポーネント構築のフレームワークだそうな。 単体テストができるのも利点なようです。
最終的に、できたのはこちら。
参考
導入 インストール Gemfile を修正してインストール。
Gemfile 1 gem "view_component", require: "view_component/engine"
コマンドでひな形作成。
1 2 3 4 5 6 7 bash-4.2 Running via Spring preloader in process 5524 create app/components/sidebar_component.rb invoke test_unit create test /components/sidebar_component_test.rb invoke erb create app/components/sidebar_component.html.erb
作成されたファイルは以下のようになります。
app/components/sidebar_component.rb 1 2 3 4 5 class SidebarComponent < ViewComponent::Base end
app/components/sidebar_component.html.erb 1 <div > Add Sidebar template here</div >
test/components/sidebar_component_test.rb 1 2 3 4 5 6 7 8 9 10 require "test_helper" class SidebarComponentTest < ViewComponent::TestCase def test_component_renders_something_useful end end
実装 適当なページを作って、作った ViewComponent を使ってみます。 ViewComponent の名前の通り、サイドバーを切り出してみます。
呼び出し元のコントローラーなどは今回の主題ではないので、テンプレートから ViewComponent の使用を書いていきます。
views/home/index.html.erb 1 2 3 4 5 6 <main > <div > メインコンテンツ</div > </main > <aside > <%= render(SidebarComponent .new) %> </aside >
このような記述で、次のような表示になります。
えっ、サイドバーじゃないじゃん、ダメじゃん。 な見た目ですが、CSS での調整をしていないのでとりあえず表示されていれば OK です。
テスト さて、独立したテストができるというのがポイントです。 テストを用意してみます。
test/components/sidebar_component_test.rb 1 2 3 4 5 6 7 8 9 10 require "test_helper" class SidebarComponentTest < ViewComponent::TestCase def test_component_renders_something_useful assert_equal( %(<div>Add Sidebar template here</div>) , render_inline(SidebarComponent .new).css("div" ).to_html ) end end
実行するとこんな様子。
1 2 3 4 5 6 7 8 9 10 bash-4.2 Running via Spring preloader in process 6373 Run options: --seed 30463 . Finished in 0.203129s, 4.9230 runs/s, 4.9230 assertions/s. 1 runs, 1 assertions, 0 failures, 0 errors, 0 skips
テスト通過できました。
ViewComponent に値を渡す 呼び出し元になるテンプレートから ViewComponent へ値を渡すことができます。
オブジェクトの引数として views/home/index.html.erb 1 2 3 4 5 6 <main > <div > メインコンテンツ</div > </main > <aside > <%= render(SidebarComponent .new(user: user)) %> </aside >
app/components/sidebar_component.rb 1 2 3 4 5 6 7 class SidebarComponent < ViewComponent::Base def initialize (text: ) @text = text end end
app/components/sidebar_component.html.erb 1 2 <div > Add Sidebar template here</div > <div > <%= @text %></div >
動作させると次のようになります。
パラメータとして渡した文字列を反映できました。
with_content の引数として また、with_content メソッドも使用できます。
views/home/index.html.erb 修正 1 2 3 4 5 6 <main > <div > メインコンテンツ</div > </main > <aside > <%= render(SidebarComponent .new.with_content("任意のテキスト" )) %> </aside >
app/components/sidebar_component.rb 1 2 3 4 class SidebarComponent < ViewComponent::Base end
app/components/sidebar_component.html.erb 1 2 <div > Add Sidebar template here</div > <div > <%= content %></div >
この実装でも同じように使用できます。
コントローラーからのレンダリング コントローラーからのレンダリングもできるそうですが Rails6.1 未満は使えないようです。
turbo frame でのコンテンツ遅延ロードなど使う場合には、コントローラーから ViewComponent のレンダリングが有効に使えそうでした。
コレクションのレンダリング ここまでは、単一のコンポーネントをレンダリングしましたが、続けてコレクションのレンダリングを試します。
ひな形を作成。
1 2 3 4 5 6 7 bash-4.2 Running via Spring preloader in process 6476 create app/components/item_component.rb invoke test_unit create test /components/item_component_test.rb invoke erb create app/components/item_component.html.erb
作成したひな形を以下のように修正します。
app/components/item_component.rb 1 2 3 4 5 6 7 8 9 10 11 12 13 class ItemComponent < ViewComponent::Base with_collection_parameter :link def initialize (link: , link_counter: ) @link = link @counter = link_counter end end
app/components/item_component.html.erb 1 2 3 <li > <%= link_to @link [:title ], @link [:url ] %> </li >
呼び出し側は次のようになります。
views/home/index.html.erb 1 2 3 4 5 6 7 8 9 <main > <div > メインコンテンツ</div > </main > <aside > <%= render(SidebarComponent .new(text: "任意のテキスト" )) %> <ol > <%= render(ItemComponent .with_collection([{title: "github" ,url: "https://github.co.jp/" }, {title: "Amazon" , url: "https://www.amazon.co.jp/" }])) %> </ol > </aside >
表示すると次のようになります。
リストのレンダリングができました。
本格的にサイドバーを実装してみる bulma を導入 CSS を 1 から書くのも苦しいので bulma を導入します。
1 gem "bulma-rails", "~> 0.9.1"
実装 以下のように実装しました。
app/views/home/index.html.erb 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <div class ="container" > <div class ="columns" > <div class ="column is-9 " > <h2 > メインコンテンツ</h2 > <div > <p > 吾輩わがはいは猫である。名前はまだ無い。・・・・・ </p > </div > </div > <div class ="column is-3 " > <%= render(SidebarComponent .new) %> </div > </div > </div >
app/components/sidebar_component.rb 1 2 3 4 5 6 7 8 9 10 11 class SidebarComponent < ViewComponent::Base def initialize @items = [ {title: "github" ,url: "https://github.co.jp/" }, {title: "Amazon" , url: "https://www.amazon.co.jp/" } ] end end
app/components/sidebar_component.html.erb 1 2 3 4 5 6 7 <aside class ="menu" > <p > Links</p > <ul class ="menu-list" > <%= render(ItemComponent .with_collection(@items )) %> </ul > </div > </aside >
app/components/item_component.rb 1 2 3 4 5 6 7 class ItemComponent < ViewComponent::Base def initialize (link: ) @link = link end end
app/components/item_component.html.erb 1 2 3 <li > <%= link_to @link [:title ], @link [:url ] %> </li >
動作させると次のようになります。
何故、コンテンツ本体が『吾輩は猫である』冒頭で、リンク先が github と amazon なのか?はあるものの動作確認できました。
View Component を使ってみました。Rails】ViewComponent と Partial のパフォーマンスを比較 という記事があり、パフォーマンスでも有利な点がありそうです。
今回は取り扱ってませんが、view でしか使わないメソッドを ViewComponent に切り出してあげることができます。 特定の view でしか使わないのに helper に書くよりも見通しがよさそうです。
ではでは。