10分で覚えるRailsにview componentsを入れる理由とどうやって実際の作業方法

6 min読了の目安(約6000字TECH技術記事

新しい技術覚えるのしんどい……と思ったかもしれませんが、開発前にこれだけ覚えておけばバッチグーです

view componentはmustな技術なので概念だけでも覚えて帰って下さい(実際に使います)

実際にview componentはどんな時に使うの?

多分最初に気になるのはこれだと思います

この点についてはありがたいことに超ウルトラ明確で「render partialをview componentに置き換える」という事だけ覚えればOKです

今まで = render していた所を新しい技術である「View Componentで順次置き換えていく」だけで使いたいシチュエーションなんかは全部=renderと一緒、つまり「同じことをループするような部分を別のファイルに分けたい」とか「長くなりすぎたのでページを分割したい」とかそういうときです

1. = render .... は使わない

俗に言うrender partialは使わないで下さい

遅い、重い、テストできないの3重苦です

もしどうしてもviewファイルの中で= renderを使いたくなったら社内のエンジニア3名以上からハンコを貰って、経営陣の会議でプレゼンして充分な議論を行い、過半数の賛成があったらその部分だけ使ってもOKとします(要約:つかうな)

2. = render XX::Component.new を使おう

じゃあ、今まで= renderしていた部分はどうするのか?

View Componentというものを使いましょう

https://viewcomponent.org/

これはRails6.1から標準で入った機能で、githubが作っているやつです

= render ...の時は_item.hamlのようにファイル名を_で始めていたと思いますが、それと同じ感じで

view componentの場合は/app/components/item/component.hamlと、「/app/components/の中に設置する」というのを覚えておけばOKです

.

  1. 設置場所が変わった(必ず /app/components/以下にあるので分かりやすい! )
  2. 頭に_をつけるというルールがcomponent.hamlというファイル名にするというルールに変わった
  3. ファイル名はディレクトリ構成で決めるようになった(例 今まで /app/views/item/_card.hamlだったなら、/app/components/item/card/component.hamlにする)
  4. +【追加要素】 同じ場所にcomponent.rbも作って渡したい変数を処理する

ね、簡単ですよね?

.

4だけ今まで必要なかった工程なのでちょっと難しいかもしれませんが、とりあえず他の人が設置しているcomponent.rbをコピペして一番上のclass名を合わせれば動きます

もし複雑なことをやりたかったらドキュメントを読んでみて下さい。ただし、ここで色々やるのはおすすめましません

実際にはjsファイルやcssファイルやrbファイルも一緒にこの中に入るのですが、それに関しては既に作ってある他のcomponent.hamlやcomponent.rbを見てコピペすればなんとなく動くので深く理解しなくてもそれでOKです

componentについて理解する

コンポーネント化というのはどういう概念か?

コンポーネント化というのは、ざっくり言えばこんな感じです

<div component-name='follow-button'>
  // HTMLタグも
  <a href='/follow'> フォローボタン </a>

  // cssも
  <style>
    // host = コンポーネントの名前に自動で置換される
    :host > a{
      color: red;
    }
  </style>

  // jsも
  <script>
    // this = コンポーネントが自動で入る
    $(this).find('a').click(function(){
      if(confirm("本当にフォローする?")){
        return false;
      }
    })
  </script>
</div>

こんな感じで「html」も「css」も「js」も全部一個のファイルの中にまとめてしまって、外部からは

<div component-is='follow-button'></div>

「このパーツ(コンポーネント)を設置する」という形でこんな感じで呼び出せるわけです

コンポーネントにすると何が良い?

これの何が良いかと言うと、ファイルを開けばそこで使っているcssもhtmlもjsも全部書いてあるので、外部に影響を与えずその中だけ読めば全て分かるし、周りに悪影響を与えず改修できるという点です

  • このclassはどんなスタイルがあたってるんだっけ……
  • jsはどこにおいたっけ……
  • .innerってグローバルcssだっけ?.containerって他でも使ってたような……

人が増えれば増えるほどこういった悩みが増えるのですが、上のようにコンポーネント化してしまえばこんな複雑な事を一切考えなくて良くなります。だって、開いたら関係するものは全部書いてあるので(最高)

といっても現実的にcssとjsとhtmlは分離しないといけない

cssを上のように直接書いてしまうと、ページが読み込まれる度に同じ量のcssが読み込まれる事になるので(gzipで圧縮されるし、正直超微々たるものですが)あまりよろしくはありません

かと言って、わけのわからないところにjsファイルやcssファイルを置いてしまうと探しに行くのがめっちゃ面倒くさいです

従って、上のview componetnではこんな感じでcssを同じディレクトリ内に置きましょう

/app
  /components
    /follow
      /button
        /component.haml
	/component.rb
	/component.css
	/component.js

上の「全部1ファイル」よりは手間が増えますが

/app
  /components
    /follow
      /card
        /component.haml
	/component.rb
	/component.css        
      /button
        /component.haml
	/component.rb
	/component.css
	/component.js
      /header
        /component.haml
	/component.rb

こんな感じで、cssを使っているのか?使っていないのか?jsを使っているのか?使っていないのか?が一目でわかります

実際にはここに書いたファイルをgulpが良い感じにアレコレして設置してくれる仕組みになっています

cssはtailwindで書こう

さて、cssも置けると書いたのですが「ファイル数は少ないに越したことはない」ので、出来る限りcssを書かずにすませましょう

そんなあなたのために「tailwind.css」を導入しています

tailwind.cssは簡単に言うと「cssとclass名をほぼ1:1でつなげたヤツ」で、例えば<a style='font-weight:bold'>太いリンク</a>であれば<a class='font-bold'>太いリンク</a>と対応するclass名をふるだけで作ることが出来ます

これを導入する事で、例えばさっきの例↓↓

<div component-name='follow-button'>
  // HTMLタグも
  <a href='/follow'> フォローボタン </a>

  // cssも
  <style>
    // host = コンポーネントの名前に自動で置換される
    :host > a{
      color: red;
    }
  </style>

  // jsも
  <script>
    // this = コンポーネントが自動で入る
    $(this).find('a').click(function(){
      if(confirm("本当にフォローする?")){
        return false;
      }
    })
  </script>
</div>

これを、こんな感じでstyleを対応するclassと交換して書くことが出来るようになります

<div component-name='follow-button'>
  // HTMLタグも        ↓ここにclassが入った
  <a href='/follow' class='color-red-500'> フォローボタン </a>

  // cssも
  <style>
    // classで書いたのでcssがいらなくなった
  </style>

  // jsも
  <script>
    // this = コンポーネントが自動で入る
    $(this).find('a').click(function(){
      if(confirm("本当にフォローする?")){
        return false;
      }
    })
  </script>
</div>

実際に実務で使うコンポーネントファイルの例で言えば

/app
  /components
    /follow
      /button
        component.haml
	component.rb
	component.js
	# => component.css もういらない

cssが無くなったことで、こんな感じでファイル数を1つ減らすことが出来ます。ファイル数が減る=開かなくてもいいファイルが増える=幸せ

という事で、基本的にはtailwindを使って下さい。cssを自前で書くパターンというのは、:beforeや:nth-childなどを使う時や、tailwindに存在していないものを使わないといけないパターン(-webkit-scroll-behavior:とか)だけです

ただ、存在してないパターンの場合はcssを書くよりもtailwind自体にそれを追加した方が良いですし、なんならそれだけinline styleで書いても良いので、出来る限り.cssファイルは書かない、作らないようにしましょう

jsは?

jsに関してはstimulus.jsというものを導入しています

これはDHH(Railsの生みの親)が作ったフレームワークで、他のjsフレームワークと違う点として「Railsが吐き出したhtmlを拡張する」というのに主眼が置かれているという特徴があります

誰でもすぐに理解できるくらいルールが少ないので、ルールに関してはリファレンスを読んで下さい

https://stimulusjs.org/reference/controllers

.

さて、うちの開発環境で追加で覚えないといけないルールを説明しておきます

それは「コントローラー名は自動で計算されるので#{this}を使う」という事です

本来であればcontrollerの名前は自分でつける必要があるため、例えばfollow/button/componentであればdata-controller="rails-components--follow-button-component"などになるのですが、これを手で打つのはめんどくさいですし、バグを生む温床になる&改修に弱くなります

従って、data-controllerには="#{this}"を指定してください

これはapplication_component.rbにおいて

  def this
    "rails-components--#{self.class.to_s.underscore.gsub('/', '_').gsub('_', '-')}"
  end

このように設定されていて、上の面倒くさい命名を自動でしてくれます

設定よりも規約 is best

あとは、上にも書いたのですが

/app
  /components
    /follow
      /button
        /component.rb
	/component.haml
	/component.js # ここに設置する

ここに設置して下さい

このjsファイルは自動的にwebpackの管轄にコピペされます(foreman sでスタートしていない場合は自動でgulpが動かないので注意)

これも実際の例をあげると

:ruby
  data = {
    controller: this,
    "#{this}-id-value": uuid,
    "#{this}-index-value": @index
  }

.swipers-menu{data: data}
  .z-10.bg-sitebg-dark
    .flex.is_scroll.overflow-x-scroll.no-scrollbar{data: {"#{this}-target": :menus}}

こんな感じですね、コントローラーの名前に#{this}を使っている事に注意して下さい

その他

あとでもう少し追記します

特にstimulus.jsを3分で理解できる説明みたいなものを書きたい所存