Vue.js(ver 2.x/3.x)コンポーネント定義の方法整理してみた

# はじめに

ポートフォリオ制作真っ最中です。
コンポーネントの定義の仕方がたくさん出てきて混乱したので、備忘録の意味も含めて整理します。
初心者で同じところで混乱してしまった方に届けば嬉しいです。
先人の方々の知恵と公式の内容を引用し整理しています。
※誤りなどあれば、コメントくださると嬉しいです。

## 結論

以下によって、コンポーネントの定義の仕様が異なる。
※網羅性は担保できていないので、他にもこんなのがあるみたいなのがあれば、コメントいただければ嬉しいです。

>バージョン(2.x or 3.x )
使用するグローバルAPI(Vue.component or Vue.extend)
コンポーネントの管理の仕方(単一ファイルで管理するか or not)
TypeScriptのサポートを使うか(型推論が必要か or not)
　※使う場合、クラススタイルのVueコンポーネントの記述法となる
compositionAPIがを使うか(「ロジックの抽出と再利用」が必要か or not)

何が良い/悪いに関しては言及しません。
詳細を順を追って見ていきます。

***
# バージョン2.x
## コンポーネントの基本

app.js内にて
*ローカル定義*

```javascript
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
```

```javascript
new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})
```

*グローバル定義*

```javascript
Vue.component('sample', {
  data: function () {
    return {
      count: 0
    }
  },
  template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
```
```html
<div id="sample">
  <button-counter></button-counter>
</div>
```
#### 説明
* 基本的には、ローカル定義が推奨される。理由はJavascriptのファイルサイズを小さくできるため。
Webpack のようなビルドシステムを利用しているときに、グローバルに定義した全てのコンポーネントは、たとえ使用しなくなっても、依然として最終ビルドに含まれてしまう。
* `Vue.component`を使用し、コンポーネントを定義する
* dataオプションは参照を制限するため関数で書く
* templateで描画htmlを書く
* ルート固有のelオプションは使えない


## コンポーネントの詳細
* 基本的にケバブケース(xxxx-xxxx)が用いられる。
* パスカルケース(XxxxXxxx)も用いられるがDOM内で使用できないため、基本的にはケバブケース

* **ローカル定義されたコンポーネントは、他のサブコンポーネント内では使用できない**
> ComponentA を ComponentB 内で使用可能にする場合

componentオプションでサブコンポーネントを定義する

```javascript

var ComponentA = { /* ... */ }

var ComponentB = {
  components: {
    'component-a': ComponentA
  },
  // ...
}
```


ES2015モジュールを利用しているのならば、以下のように記載
ComponentAをインポートしている、export defaultで定義しているなどの点が異なる

```javascript

import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  },
  // ...
}
```

## 単一ファイルコンポーネント

```vuejs

<template>
　<div id="app">
　　<h1>My Todo App!</h1>
　　　<TodoList/>
　</div>
</template>

<script>
import TodoList from './components/TodoList.vue'

export default {
　components: {
　　TodoList
　}
}
</script>
```

**説明**

以下の3つのタグで構成される。
* `<template>`
* `<script>`
* `<style>`

上からそれぞれ、描画するDOM、Vueインスタンス等の記述、cssを表す

## vue.extendグローバルAPIのコンポーネント定義
グローバルAPIのvue.extendを用いたコンポーネントの定義の仕方

```javascript

var sample = Vue.extend({
  template: '<h1>Sample</h1>'
})
```

**vue.extend()とvue.componentの違い**
>Vue.extend()はクラス継承メソッド。Vueのサブクラスを作成し、コンストラクターを返す。

>Vue.component()は、Vue.directive()およびVue.filter()に似たアセット定義メソッド。Vue.jsがテンプレートで指定されたコンストラクターをストリングIDに関連付ける。実はオプションをVue.component()に直接渡すと、内部でVue.extend()を呼び出している。

## クラススタイルのコンポーネント定義(TypeScriptの型推論を使用する場合)

公式にメンテナンスされている`vue-class-component`のデコレータをimportし定義する
また、アノテーション(@;関連データの注釈のコード)を付与する

```javascript
import Vue from 'vue'
import Component from 'vue-class-component'

// @Component デコレータはクラスが Vue コンポーネントであることを示します
@Component({
  // ここではすべてのコンポーネントオプションが許可されています
  template: '<button @click="onClick">Click!</button>'
})
export default class MyComponent extends Vue {
  // 初期データはインスタンスプロパティとして宣言できます
  message: string = 'Hello!'

  // コンポーネントメソッドはインスタンスメソッドとして宣言できます
  onClick (): void {
    window.alert(this.message)
  }
}
```


***


# バージョン3.x
## 前提
コンポーネントの定義以外もバージョン2.xとは諸々異なる。
今回は後述のコンポーネントの定義に必要なVueインスタンスの生成の相違点のみ説明する

**Vueインスタンスの生成**

```javascript

// バージョン3.x

Vue.createApp({
  data() {
    return {
        xxxx: 'sample',
    }
  }
})
```
```javascript

// バージョン2.x

var vm = new Vue({
  el: '#example',
  data: {
    xxxx: 'sample'
  },
})
```


## コンポーネントの基本


app.js内にて

>ちなみに、本セクションの仕様(2.xでのコンポーネント定義方法)は`OptionAPI`と呼ばれる
>3.xにて新しく導入された`CompositionAPI`とよく比較される

*ローカル定義*

```javascript

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
```

```javascript

new Vue({
const app = Vue.createApp({
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})
```
componentsオプションの使用などバージョン2.xと殆ど変わらない。
Vueインスタンスの生成のみ異なる。


*グローバル定義*

```javascript

// Vue アプリケーションを作成
const app = Vue.createApp({})

// グローバルな button-counter というコンポーネントを定義します
app.component('button-counter', {
  data() {
    return {
      count: 0
    }
  },
  template: `
    <button @click="count++">
      You clicked me {{ count }} times.
    </button>`
})
```
```html
<div id="sample">
  <button-counter></button-counter>
</div>
```
#### 説明
* <2.xと同様>基本的には、ローカル定義が推奨される。理由は2.xと同様
* 作成したアプリケーションappのコンポーネント`app`を使って`app.component`とし、コンポーネントを定義する
* dataオプションは2.xでは関数で書く仕様だったが、3.xはデフォルトで関数で書くようになっている
* <2.xと同様>templateで描画htmlを書く
* マウントする場合は、`app.mount('#{id}')`でidを指定しマウントする


## コンポーネントの詳細

* <2.xと同様>基本的にケバブケース(xxxx-xxxx)が用いられる。
* <2.xと同様>パスカルケース(XxxxXxxx)も用いられるがDOM内で使用できないため、基本的にはケバブケース

* <2.xと同様>**ローカル定義されたコンポーネントは、他のサブコンポーネント内では使用できない**
> ComponentA を ComponentB 内で使用可能にする場合

2.xと全く同じのため割愛


## 単一ファイルコンポーネント

```javascript

<template>
  <div class="xxxx">
  </div>
</template>

<script>
import xxxx from "./xxxx.vue"

export default {
  components: {
    TodoItem
  },

  data() {
    return {
    }
  },

  methods: {
  }
}
</script>
```

dataオプションの書き方が少し変わりますが、それ以外は全く変わらない。

## defineComponent グローバル API
2.xの`vue.extend`と同じような位置づけでコンポーネントを定義できます。

```javascript

const sample = Vue.defineComponent({
  template: '<h1>Sample</h1>'
```

## Composition API
3.xにて新しく導入されたComposition APIなので詳細に書きます。

>「”なぜ” Composition APIを使うのか」
その理由は、
「ロジックの抽出と再利用をするため」

とのこと。

>「”なぜ” ロジックの抽出と再利用をするのか」
その理由は、
「複雑に肥大化したコンポーネントを、小分けにして関心事で分別し、クリーンな状態に整理ため」

### 「結論」コードの整理により可読性、メンテナンス性、柔軟性を高めるためComposition APIを使用するとのことです。

compositionAPIが特にわからなかったので、こちらの記事を参考にさせていただきました。
ありがとうございます！

[なぜ、Vue Composition APIを使うのか、理解する【メリット/デメリットまとめ】]([https://qiita.com/karamage/items/7721c8cac149d60d4c4a](https://)
)

### 具体的な書き方
Composition API特有のオプションを確認します。

#### `setupオプション`
コンポーネントが作成される前に`props`が解決されると実行され、コンポジション API のエントリポイントとして機能。
setup が実行されたときは、まだコンポーネントのインスタンスが作られないため、setup オプションの中では this を使用できません。アクセスできるプロパティは`props``context`(`attrs`, `slotsm`, `emit`)のみです。つまり前述したようなプロパティ(`data`, `computed`, `methods`)にはアクセスできません。

setup関数は2つの引数を取ります。第一引数は`props`引数、第二引数は `context`引数です。
まずsetup関数内の`props`はリアクティブで、新しい`props`が渡されたら更新されます。


具体的なコード

```javascript

// MyBook.vue

export default {
  props: {
    title: String
  },
  setup(props) {
    console.log(props.title)
  }
}
```
次に`context`は非リアクティブです。参照をいじりたくない場合に使用します。

具体的なコード

```javascript

// MyBook.vue
export default {
  setup(props, { attrs, slots, emit }) {
    ...
  }
}
```

詳細はこちらの記事を参考にしてもらえればと思います。
[Vue 2.xのOptions APIからVue 3.0のComposition APIへの移行で知っておくと便利なTips](https://techblog.zozo.com/entry/vue-options-api-to-composition-api)
## TypeScriptのサポート
TypeScriptで適切に型推論を行うために前述したdefineComponentグローバルAPIを用いて、コンポーネントを定義する必要があります。

これにより型推論が有効化されます。

```javascript

import { defineComponent } from 'vue'

const Component = defineComponent({
})
```
また、OptionAPIでもCompositionAPIでもともに利用することができます。
2.xでは、クラススタイルのコンポーネント定義を行いましたが、それをdefineComponentの定義に変えることで使用できます。

以下の例では`type`を使用し型推論を定義しています。

```javascript
import { defineComponent, PropType } from 'vue'

interface ComplexMessage {
  title: string
  okMessage: string
  cancelMessage: string
}
const Component = defineComponent({
  props: {
    name: String,
    success: { type: String },
    callback: {
      type: Function as PropType<() => void>
    },
    message: {
      type: Object as PropType<ComplexMessage>,
      required: true,
      validator(message: ComplexMessage) {
        return !!message.title
      }
    }
  }
})
```
以下はCompositionAPIと併用した例です。
同様にdefineComponentAPIを使用します。

```javascript

import { defineComponent } from 'vue'

const Component = defineComponent({
  props: {
    message: {
      type: String,
      required: true
    }
  },

  setup(props) {
    const result = props.message.split('') 
    const filtered = props.message.filter(p => p.value) 
  }
})
```
以上、個人的にまとめましたが、多々表記ゆれや誤りなども含みうると思いますので、ご指摘貰えれば嬉しいです。

■参考にさせてもらった記事
>https://optimizely.github.io/vuejs.org/guide/composition.html
>https://jp.vuejs.org/v2/guide
>https://qiita.com/karamage/items/7721c8cac149d60d4c4a
>https://techblog.zozo.com/entry/vue-options-api-to-composition-api
>https://qiita.com/karamage/items/7721c8cac149d60d4c4a