はじめに
ポートフォリオ制作真っ最中です。
コンポーネントの定義の仕方がたくさん出てきて混乱したので、備忘録の意味も含めて整理します。
初心者で同じところで混乱してしまった方に届けば嬉しいです。
先人の方々の知恵と公式の内容を引用し整理しています。
※誤りなどあれば、コメントくださると嬉しいです。
結論
以下によって、コンポーネントの定義の仕様が異なる。
※網羅性は担保できていないので、他にもこんなのがあるみたいなのがあれば、コメントいただければ嬉しいです。
バージョン(2.x or 3.x )
使用するグローバルAPI(Vue.component or Vue.extend)
コンポーネントの管理の仕方(単一ファイルで管理するか or not)
TypeScriptのサポートを使うか(型推論が必要か or not)
※使う場合、クラススタイルのVueコンポーネントの記述法となる
compositionAPIがを使うか(「ロジックの抽出と再利用」が必要か or not)
何が良い/悪いに関しては言及しません。
詳細を順を追って見ていきます。
バージョン2.x
コンポーネントの基本
app.js内にて
ローカル定義
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
new Vue({
el: '#app',
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
グローバル定義
Vue.component('sample', {
data: function () {
return {
count: 0
}
},
template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
})
<div id="sample">
<button-counter></button-counter>
</div>
説明
- 基本的には、ローカル定義が推奨される。理由はJavascriptのファイルサイズを小さくできるため。
Webpack のようなビルドシステムを利用しているときに、グローバルに定義した全てのコンポーネントは、たとえ使用しなくなっても、依然として最終ビルドに含まれてしまう。 -
Vue.component
を使用し、コンポーネントを定義する - dataオプションは参照を制限するため関数で書く
- templateで描画htmlを書く
- ルート固有のelオプションは使えない
コンポーネントの詳細
-
基本的にケバブケース(xxxx-xxxx)が用いられる。
-
パスカルケース(XxxxXxxx)も用いられるがDOM内で使用できないため、基本的にはケバブケース
-
ローカル定義されたコンポーネントは、他のサブコンポーネント内では使用できない
ComponentA を ComponentB 内で使用可能にする場合
componentオプションでサブコンポーネントを定義する
var ComponentA = { /* ... */ }
var ComponentB = {
components: {
'component-a': ComponentA
},
// ...
}
ES2015モジュールを利用しているのならば、以下のように記載
ComponentAをインポートしている、export defaultで定義しているなどの点が異なる
import ComponentA from './ComponentA.vue'
export default {
components: {
ComponentA
},
// ...
}
単一ファイルコンポーネント
<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を用いたコンポーネントの定義の仕方
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し定義する
また、アノテーション(@;関連データの注釈のコード)を付与する
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インスタンスの生成
// バージョン3.x
Vue.createApp({
data() {
return {
xxxx: 'sample',
}
}
})
// バージョン2.x
var vm = new Vue({
el: '#example',
data: {
xxxx: 'sample'
},
})
コンポーネントの基本
app.js内にて
ちなみに、本セクションの仕様(2.xでのコンポーネント定義方法)は
OptionAPI
と呼ばれる
3.xにて新しく導入されたCompositionAPI
とよく比較される
ローカル定義
var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }
new Vue({
const app = Vue.createApp({
components: {
'component-a': ComponentA,
'component-b': ComponentB
}
})
componentsオプションの使用などバージョン2.xと殆ど変わらない。
Vueインスタンスの生成のみ異なる。
グローバル定義
// 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>`
})
<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と全く同じのため割愛
単一ファイルコンポーネント
<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
と同じような位置づけでコンポーネントを定義できます。
const sample = Vue.defineComponent({
template: '<h1>Sample</h1>'
Composition API
3.xにて新しく導入されたComposition APIなので詳細に書きます。
「”なぜ” Composition APIを使うのか」
その理由は、
「ロジックの抽出と再利用をするため」
とのこと。
「”なぜ” ロジックの抽出と再利用をするのか」
その理由は、
「複雑に肥大化したコンポーネントを、小分けにして関心事で分別し、クリーンな状態に整理ため」
「結論」コードの整理により可読性、メンテナンス性、柔軟性を高めるためComposition APIを使用するとのことです。
compositionAPIが特にわからなかったので、こちらの記事を参考にさせていただきました。
ありがとうございます!
なぜ、Vue Composition APIを使うのか、理解する【メリット/デメリットまとめ】
具体的な書き方
Composition API特有のオプションを確認します。
setupオプション
コンポーネントが作成される前にprops
が解決されると実行され、コンポジション API のエントリポイントとして機能。
setup が実行されたときは、まだコンポーネントのインスタンスが作られないため、setup オプションの中では this を使用できません。アクセスできるプロパティはprops``context
(attrs
, slotsm
, emit
)のみです。つまり前述したようなプロパティ(data
, computed
, methods
)にはアクセスできません。
setup関数は2つの引数を取ります。第一引数はprops
引数、第二引数は context
引数です。
まずsetup関数内のprops
はリアクティブで、新しいprops
が渡されたら更新されます。
具体的なコード
// MyBook.vue
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
次にcontext
は非リアクティブです。参照をいじりたくない場合に使用します。
具体的なコード
// MyBook.vue
export default {
setup(props, { attrs, slots, emit }) {
...
}
}
詳細はこちらの記事を参考にしてもらえればと思います。
Vue 2.xのOptions APIからVue 3.0のComposition APIへの移行で知っておくと便利なTips
TypeScriptのサポート
TypeScriptで適切に型推論を行うために前述したdefineComponentグローバルAPIを用いて、コンポーネントを定義する必要があります。
これにより型推論が有効化されます。
import { defineComponent } from 'vue'
const Component = defineComponent({
})
また、OptionAPIでもCompositionAPIでもともに利用することができます。
2.xでは、クラススタイルのコンポーネント定義を行いましたが、それをdefineComponentの定義に変えることで使用できます。
以下の例ではtype
を使用し型推論を定義しています。
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を使用します。
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