【Rails】stimulus.js × view-component やってみた

# 概要
**stimulus.js × view-component**の導入を学んだので、
記録のために残しておきます。


今回のゴールは **stimulus.js × view-component** で
以下の入力欄に文字を入力し、Greetを押すと、横に「Hello, 〇〇！」と表示されたら成功です。
![スクリーンショット 2021-10-04 15.54.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/06b5033c-45fd-f361-6f91-15f40d603d7d.png)
　　　　　　↓↓↓
![スクリーンショット 2021-10-04 15.55.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/a636df7a-5d38-7d30-f41d-9fd259413a08.png)

# 参考資料
[stimulus公式ドキュメント](https://github.com/hotwired/stimulus-rails)
[viewcomponent公式ドキュメント]
(https://viewcomponent.org/guide/getting-started.html)

# 環境
・Mac M1 バージョン11.4
・Ruby 2.6.8
・Rails 6.1.4.1

## 手順① アプリを作成

まずは以下のコマンドで新しくファイルを作成します。

```rails
% rails new stimulus-view-component
```
これで必要なファイルがインストールされました。

## 手順② stimulusをインストール

以下のURLを参考にstimulusのインストール方法を確認します。
[https://github.com/hotwired/stimulus-rails](https://github.com/hotwired/stimulus-rails)

まずはgemfileに以下を記述します。

```ruby:gemfile
gem 'stimulus-rails'
```
続いて以下のコマンドを順番に実行します。

```rails
% ./bin/bundle install.

% ./bin/rails stimulus:install
```

![スクリーンショット 2021-10-04 12.08.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/b99efca5-43d4-4ced-64e0-f85f12e5d723.png)

stimulusはインストールは完了しましたが、

```rails
You must import "./controllers" in your JavaScript entrypoint
```
私の環境では、赤字でこのようなエラーが出ました。

**「"./controllers"」**の設定をしてあげる必要があるため、
app/javascript/packs/application.js　に
**「import "controllers"」**を追記します。
以下の通りです。

```js:app/javascript/packs/application.js
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"

Rails.start()
Turbolinks.start()
ActiveStorage.start()

import "controllers"  // ← これを追記

```

これでstimulusのインストールが完了しました。

次にview-componentをインストールしていきます。

## 手順③ view-componentのインストール

view-component の公式ドキュメントを参考にしながらインストールしていきます。
[https://viewcomponent.org/guide/getting-started.html]
(https://viewcomponent.org/guide/getting-started.html)

![スクリーンショット 2021-10-04 13.39.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/c364dcb7-d7ce-b322-945c-27824d45dcfd.png)

最初に、view_componentのgemをgemfileに追記します。

```ruby:gemfile
gem "view_component", require: "view_component/engine"
```
gemの記述が完了したら以下のコマンドを実行します。![スクリーンショット 2021-10-04 13.47.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/a698e249-64f9-1389-a10a-ee1063265fda.png)


```
% bundle install
```
これでインストールが完了しました。

現時点でうまく反映できているか確認するため、
サーバーを動かします。

```
％ rails s
```

[http://localhost:3000/](http://localhost:3000/)で確認します。

以下のようになっていれば成功です。
![スクリーンショット 2021-10-04 14.13.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/5a6f2b04-6fe5-fe00-b8e8-21322de552c7.png)

## 手順④componentファイルの作成

次に以下の画像の通り実行していきます。
![スクリーンショット 2021-10-04 13.41.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/b61c0244-f294-4871-ad5f-878f9a91c761.png)

以下のコマンドでcomponentファイルを作成します。

```
% bin/rails generate component Example title
```

ファイルが以下のように作成されればOKです。
![スクリーンショット 2021-10-04 13.49.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/cfbfaf0a-492f-7a4f-b88c-310d99011bb6.png)

作成されたファイルの中の**app/components/example_component.rb**　が
以下の画像と同じになっているか確認して下さい。
![スクリーンショット 2021-10-04 13.51.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/70b856fa-f191-6b87-1a46-7c571d305405.png)

確認できたら、今度は**app/components/example_component.html.erb**
を以下のように変更します。
![スクリーンショット 2021-10-04 13.55.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/a42247cb-fdf8-92de-9e8d-003288068c0b.png)

```erb:app/components/example_component.html.erb
<span title="<%= @title %>"><%= content %></span>
```

## 手順⑤ viewファイルの作成



view配下に
**app/views/home/index.html.erb** を作成します。
![スクリーンショット 2021-10-04 14.02.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/8b46cd7b-f1d9-a5cc-9442-1ce52cb258ff.png)

これでviewファイルが作成できたので、公式ドキュメント通りに以下の記述を加えていきます。![スクリーンショット 2021-10-04 15.17.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/21e34016-c307-4a6e-5eef-b736bfdead1f.png)


```erb:app/views/home/index.html.erb
<%= render(ExampleComponent.new(title: "my title")) do %>
  Hello, World!
<% end %>
```



## 手順⑥ stimulusファイル作成

手順①で生成されたファイルの中の**app/assets/javascripts/controllers/hello_controller.js**を
下記のように書き換えます。

```js:app/assets/javascripts/controllers/hello_controller.js
// app/assets/javascripts/controllers/hello_controller.js
import { Controller } from "@hotwired/stimulus"

// Connects with data-controller="hello"
export default class extends Controller {
  static targets = [ "name", "output" ]

  greet() {
    this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
  }
}
```

さらに**app/views/home/index.html.erb**に
以下を追加します。

```erb:app/views/home/index.html.erb
<%= render(ExampleComponent.new(title: "my title")) do %>
  Hello, World!
<% end %>

↓下記を追記↓
<div data-controller="hello">
  <input data-hello-target="name" type="text">

  <button data-action="click->hello#greet">
    Greet
  </button>

  <span data-hello-target="output">
  </span>
</div>
```

これで公式ドキュメントを参考にした、セットアップは終了です。

動くか確かめてみましょう。

![スクリーンショット 2021-10-04 14.13.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/5a6f2b04-6fe5-fe00-b8e8-21322de552c7.png)


しかし、これではまだ動きません。

ルーティングとコントローラーを設定していませんでした。
それぞれ設定していきます。

```ruby:confing/routes.rb
Rails.application.routes.draw do
  root 'home#index'  #　こちらを追記
end
```

これでルーティングの設定は完了です。
続いてコントローラーを設定していきます。

```
% rails g controller home
```

作成されたコントローラーに以下を記述します。

```ruby:app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index   # こちらを追記
  end
end
```

ルーティングとコントローラーを設定が完了したのでサーバーを動かしていきます。

```
% rails s
```

[http://localhost:3000/](http://localhost:3000/)で確認します。

![スクリーンショット 2021-10-04 15.39.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/596bc0bd-92e7-9595-7365-1beb09854f64.png)
上手くいきました！！
![スクリーンショット 2021-10-04 15.54.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/06b5033c-45fd-f361-6f91-15f40d603d7d.png)
「Qitta」と入力すると
![スクリーンショット 2021-10-04 15.55.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/a636df7a-5d38-7d30-f41d-9fd259413a08.png)
「Hello, Qiita!」
と**stimulus.js × view-component**がうまく反映されてました。

# 追加実装

## ①component配下にまとめる

stimulus.js × view-component を動かすことができましたが、
今のままでは、componentに関するファイルがjavascriptやcomponentsファイルにバラバラに入っているので、今後さらに大きな実装をする際にどれがどのファイルか分かりづらくなってしまいます。
そのため、app/components/の次の配下に**example**を作成し、それぞれまとめていきます。

```
app/components/example/example_component.rb
app/components/example/example_component.html.erb
app/components/example/hello_controller.js
```

さらにこれらのファイルを分かりやすい名前に変えます。↓↓↓

```
app/components/example/component.rb
app/components/example/component.html.erb
app/components/example/component_controller.js
```
ファイル名を変えたので、それぞれのファイル内の記述も変更していきます。

### ファイル内の記述の変更
```ruby:app/components/example/component.rb
# frozen_string_literal: true

class Example::Component < ViewComponent::Base
  def initialize(title:)
    @title = title
  end

end

# ExampleComponentに「::」を追加
```

```erb:app/components/example/component.html.erb

<span title="<%= @title %>"><%= content %></span>

# ↓↓下記を追記

<div data-controller="example--component">
  <input data-example--component-target="name" type="text">

  <button data-action="click->example--component#greet">
    greet
  </button>

  <span data-example--component-target="output">
  </span>
</div>
```

```erb:app/views/home/index.html.erb

<%= render(Example::Component.new(title: "my title")) %>

# ↑↑↑ExampleComponentに「::」を追記↑↑↑

#　↓↓下記を削除↓↓
<div data-controller="hello">
  <input data-hello-target="name" type="text">

  <button data-action="click->hello#greet">
    Greet
  </button>

  <span data-hello-target="output">
  </span>
</div>
```

```js:app/components/example/component_controller.js

import { Controller } from "@hotwired/stimulus"

export default class extends Controller {
  static targets = [ "name", "output" ]

  
  greet() {
    this.outputTarget.textContent = `Hello, ${this.nameTarget.value}!`
  }
}
```

これでそれぞれのファイルの書き換えは完了しました。

##Stimulusの書き方変更

続いてstimulusの公式ドキュメントを見ながら、
example階層にした際の書き方に変更していきます。


![スクリーンショット 2021-10-06 11.42.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/328e74c8-0e5e-63e9-51be-e4c035ef7b35.png)

```js:app/javascript/index.js
import { Application } from "stimulus"
import { definitionsFromContext } from "stimulus/webpack-helpers"

const application = Application.start()
const context = require.context("controllers", true, /\.js$/)
const contextComponents = require.context("../../components", true, /_controller\.js$/)
application.load(
  definitionsFromContext(context).concat(
    definitionsFromContext(contextComponents)
  )
)
```

少し記述を変えていきます。

```js:app/javascript/index.js
import { Application } from "@hotwired/stimulus"
import { definitionsFromContext } from "@hotwired/stimulus-webpack-helpers"

const application = Application.start()
const context = require.context("controllers", true, /\.js$/)
const contextComponents = require.context("../../components", true, /_controller\.js$/)
application.load(
  definitionsFromContext(context).concat(
    definitionsFromContext(contextComponents)
  )
)
```

**require.context("../../components", true, /_controller\.js$/)**
これによってcomponents配下のcontrollerを一斉に動かすことができるようになります。

**「@hotwired」**のエラーができる方は
**package.json**のファイルを書き換えて
yarn installを実行します。

```json:package.json

# 下記を追記↓↓

"@hotwired/stimulus": "^3.0.0",
"@hotwired/stimulus-webpack-helpers": "^1.0.0",
```

```
% yarn install
```

これで@hotwiredに必要なものはインストールできました。

最後に下記を実行していきます。

![スクリーンショット 2021-10-06 11.58.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/c32cfb38-47ad-a4bc-d53c-85a9d0f991c2.png)

```yml:confing/webpacker.yml

# Additional paths webpack should lookup modules
# ['app/assets', 'engine/foo/app/assets']
additional_paths: ["app/components"]

# []に"app/components"を追記

```

これでexample階層を新しく作成し、componen配下をまとめることができました。
動くか確認します。

![スクリーンショット 2021-10-04 15.55.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/a636df7a-5d38-7d30-f41d-9fd259413a08.png)

無事動きました！！

＊動かない場合は、ターミナル上で**「command＋R**」を押すことでスーパーリロードを実行し、
改善されるかもしれません。

##②Stimulusのデフォルトメソッド
![スクリーンショット 2021-10-07 10.39.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1129869/8efc2745-1a08-69a1-c413-481580ac1080.png)

###initialize()
一度、コントローラーが最初にインスタンス化されたときに呼び出される

###connect()
コントローラがDOMに接続されているときはいつでも呼び出される

###[name]TargetConnected(target: Element)
ターゲットがDOMに接続されているときはいつでも呼び出される

###disconnect()
コントローラがDOMから切断されたときはいつでも呼び出される

###[name]TargetDisconnected(target: Element)
ターゲットがDOMから切断されたときはいつでも呼び出される

# 最後に
stimulus.js × view-component は以上になります。
〇〇×〇〇やってみたシリーズは今後も学習するので、
都度、新しく投稿していきます。

[【Docker】Quickstart : Compose and Rails やってみた](https://qiita.com/SAPPPOROBEER/items/65e3c08bd2e59e05bba6)