サクサクvue.jsで 単一ファイルコンポーネント(Single-file component)を使ってみる

FNCT創造工学演習、予備実験向けにハンズオンを作成しました。

# コンポーネントファイルの作成
componentsディレクトリに、新しくコンポーネントファイルを作成する。
![スクリーンショット 2021-04-20 9.55.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69744/4f38df84-3d9a-3011-6bc6-0482a41cac6c.png)

ファイル名は`TextInput.vue`としておく。
拡張子`.vue`にすると、vue.jsの単一ファイルコンポーネントとして利用できる。
![スクリーンショット 2021-04-20 9.56.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69744/9a4f73ea-404a-ae91-cba3-d39abc865e30.png)

作成したファイルに、下記のコードを記述する。

```:components/TextInput.vue
<template>
  <div>
    <label>名前</label>
    <input type="text" />
  </div>
</template>
```

このコンポーネントを、HelloWorld.vue側に組み込んで使ってみる。
スクリプト部に下記のコードを追加しよう。

```:components/HelloWorld.vue
<script>
import TextInput from './TextInput'; // この行

export default {
  name: 'HelloWorld',
  components: {       // この行
    TextInput         // この行
  },                  // この行
  data() {          
    return {       
...
}
</script>
```

最後に同じくHelloWorld.vueのテンプレート部に、下記のコードを追加する。
これでHTMLにテンプレートが挿入される。

```:components/HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <TextInput />   <!-- この行 -->          
    ...
</template>
```

このようにラベルとテキストボックスが表示されれば成功だ。

![スクリーンショット 2021-04-20 11.55.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69744/7aec96cf-cd00-f468-ca94-1e44f68f01d3.png)

さらに連続コピペすれば、同一部品が使い回せているのが分かる。

```:components/HelloWorld.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <TextInput />   <!-- この行 -->          
    <TextInput />   <!-- この行 -->          
    <TextInput />   <!-- この行 -->          
    ...
</template>
```

![スクリーンショット 2021-04-20 11.56.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69744/aa0b0227-1874-6e23-5940-8725d32ddd77.png)

# パラメータを渡して、差し替え可能なつくりにする

このままだと「名前」というラベルしか表示できないので、そこを差し替えられるようにしたい。
今回はプロパティという仕組みを使って実現してみる。

まずはTextInput.vueを下のように修正する。
`<label>`タグ内を修正し、下半分のスクリプト部をまるごと追加する。

```:components/TextInput.vue
<template>
  <div>
    <label>{{title}}</label> <!-- ここ -->
    <input type="text" />
  </div>
</template>

<script>
export default {
    props: {
        title: {
            type: String,
            default: 'デフォルト'
        }
    }
}
</script>
```

加えて利用側のHelloWorld.vueを、下記のように修正する。

```:components/HelloWorld.vue
    <TextInput title="名前" />       
    <TextInput title="メールアドレス" /> 
    <TextInput />          
```

これで、下のようにラベルが差し替えられたはずだ。
また3つめのように`label="..."`を省略すると、`デフォルト`と表示されるようにもなっている。

![スクリーンショット 2021-04-20 12.02.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69744/875a7de1-4baf-6bb4-93a3-a872dad0b4ca.png)

# 見栄えを調整する

最後にスタイル記述を追加して、見栄えを調整してみよう。
TextInput.vueにスタイル部を追加（と、`<div>`にクラスを追記）していく。

```:components/TextInput.vue
<template>
  <div class="text-input"> <!-- classを追加 -->
  ...
</template>

<script>
...
</script>

<style scoped>
.text-input {
    margin: 0 auto 20px;
    width: 300px;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
}

.text-input label {
    color: #808080;
    font-weight: bold;
}

.text-input input {
    width: 100%;
    padding: 5px 10px;
    border: 1px solid #444;
    border-radius: 3px;
}
</style>
```

簡単にスタイルを適用したが、結果はこのように表示される。

![スクリーンショット 2021-04-20 13.56.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/69744/40209246-93aa-8977-5109-bbc8e2962ebc.png)

ここでポイントなのは`<style scoped>`の部分。
`scoped`記述があると、そこで書いたスタイルはファイル内のコンポーネントにだけ適用される。
サイト全体のスタイルを命名規則を統一して管理し、完成後も長期間にわたってそのルールを厳守してメンテしていくのは非常に大変なので、`scoped`を適切に利用して管理する範囲を狭めてやることで、開発・運用にかかるコストを抑えていきたい。

# 単一ファイルコンポーネントのポイント
vue.jsの特徴的な機能のひとつである単一ファイルコンポーネントだが、このファイルは3つの要素から構成される。

- &lt;template&gt;
- &lt;script&gt;
- &lt;style&gt;

それぞれ、`<template>`でHTMLの構造を、`<script>`で実行すべき処理を、`<style>`で見栄えを記述できる。
この3要素を組み合わせてひとつの部品を、さらに部品を組み合わせて複合された部品を作り上げていく。
こうして適切に分割された部品単位で開発していくことで、管理すべき対象を適切にスリム・シンプルにしていける。

# そのほかの主な機能
vue.jsのコンポーネントの主な機能として、下記のようなものがある。

### slot
親側で記述した内容を、コンポーネント側にそのまま挿入する。

### $emit()
コンポーネントの親（この場合TextInputからHelloWorldに対して）イベントを発行する。

### v-model
少々複雑になるが、`props`と`$emit()`を組み合わせると、`v-model`という仕組みが実現できる。
今回の例でいうと、TextInputで入力されたテキストをHelloWorld側で簡単に使える（参照＆設定ができる）ようになる。