【Nuxt 2】コンポーネントの基礎
コンポーネントとは
コンポーネントは、直訳すると構成要素や部品のことです。 WEB の構成要素といえば、ヘッダー、フッター、サイドバーなど、さらに細かい単位でボタン、テキストフィールドなどが考えられます。
NuxtJS では、コンポーネントを 1 つの vue ファイル(単一ファイルコンポーネント)として作成します。 そしてこれらを組み合わせることでページを構成します。
ページやレイアウトも単一ファイルコンポーネントで作成します。
ページ、レイアウト以外のコンポーネントは/components
フォルダに作成します。
コンポーネントの関係性は、よく親子関係で表現されます。 コンポーネント A からコンポーネント B を使用する場合、A が親、B が子というように表現します。
単一ファイルコンポーネント
単一ファイルコンポーネントは、1 つのコンポーネントに対する HTML、JavaScript、CSS を定義します。
<template>
<!-- HTML部分 -->
</template>
<script>
export default {
//JavaScript(Vue.js)部分
}
</script>
<style scoped>
/* CSS部分 */
</style>
template
template
には、コンポーネントの HTML 要素を定義します。
注意点として、必ずルート要素が 1 つとなるようにします。
<template>
<div>
<p>{{ counter }}, {{ doubleCounter }}</p>
<button @click="count">count up</button>
</div>
</temaplte>
<template>
<p class="count-text">{{ counter }}, {{ doubleCounter }}</p>
<button @click="count">count up</button>
</temaplte>
script
script
には、JavaScript、主に Vue.js の定義をします。
export default
内に、data
、methods
、computed
などオプションオブジェクトの内容を定義します。
この定義については、通常の Vue.js と変わりはありません。
注意点として、data
は関数として定義してください。
<script>
export default {
data: () => ({
counter: 0,
}),
metdhos: {
count() {
this.counter++
},
},
computed: {
doubleCounter() {
return 2 * this.counter
},
},
}
</script>
style
style
には、コンポーネントに対する CSS を定義します。
scoped
とすることで、ここで定義した CSS はこのコンポーネントでのみ適用されます。
<script scoped>
.count-text {
color: red;
font-size: 12px;
}
</script>
コンポーネント名
コンポーネント名は、ファイル名と同じになります。 ファイル名はパスカルケース(アッパーキャメルケース)で命名します。
例えばSampleComponent.vue
とした場合は、<SampleComponent>
または<sample-component>
としてページなどから呼び出します。
<template>
<div>
<sample-component></sample-component>
</div>
</template>
コンポーネントの読み込み
コンポーネントを使用する際は、以下のように読み込む必要があります。
<script>
import SampleComponent from '~/components/SampleComponent'
export default {
components: {
SampleComponent,
},
}
</script>
まずimport
によって対象の単一ファイルコンポーネントを読み込みます。
パスの中の~
はプロジェクトのルートフォルダを意味します。
次にcomponents
プロパティによって、読み込んだ単一コンポーネントを指定します。
NuxtJS v2.13 以上では、nuxt/components というモジュールが搭載されており、自動で読み込まれるようになっています。 よって上記の記載は不要になります。
コンポーネント名はファイル名と同じになります。
SampleComponent.vue
であれば、<sample-component />
として使用できます。
components
フォルダが階層になっている場合、例えば/components/util/SampleComponent.vue
となっている場合は、
<util-sample-component />
のようにフォルダ名を含めます。
フォルダ階層を無視して<sample-component />
としたい場合は、nuxt.config.js
に以下の設定を行います。
export default {
components: [
{
path: '@/components/',
pathPrefix: false
}
]
}
プロパティ
プロパティとは
コンポーネントでは、オプションオブジェクトにプロパティ(props
)を定義することができます。
以下の例を見てください。
<template>
<button :style="styleObj">my-button</button>
</template>
<script>
export default {
props: {
bgColor: {
type: String,
default: '',
validator: (val) => ['blue', 'red'].includes(val)
}
},
computed: {
styleObj() {
return this.bgColor ? { background: this.bgColor } : {}
}
}
}
</script>
<template>
<div>
<my-button></my-button>
<my-button bg-color="blue"></my-button>
<my-button bg-color="red"></my-button>
</div>
</template>
プロパティは、props
にキャメルケースで定義します。
ここではbgColor
というプロパティを定義しています。
1 つのプロパティに対して、以下の定義を行います。 これらの定義は必須ではありませんが、定義しておくことで開発時の予期せぬエラーを防ぐことができます。
定義 | 説明 |
---|---|
type | プロパティのデータ型(String 、Number 、Boolean 、Array 、Object ) |
default | プロパティの指定がなかった時のデフォルト値 |
required | プロパティの指定が必須か(true or false ) |
validation | プロパティのバリデーション関数 |
bgColor
では、String
型、指定がない場合は''
が設定され、かつ'blur'
、'red'
以外の値が指定された場合はエラーとなります。
プロパティへのアクセスは、data
と同じくthis
を付けます。
プロパティの指定は、bg-color="blue"
のようにケバブケースを用います。
これによって、コンポーネントのbgColor
に'blue'
が設定されます。
プロパティはデータバインドすることで動的に変化させることができます。
<template>
<div>
<my-button :bg-color="color"></my-button>
<button @click="changeColor">change color</button>
</div>
</template>
<script>
export default {
data: () => ({
color: 'blue',
}),
methods: {
changeColor() {
this.color = this.color === 'blue' ? 'red' :'blue';
}
}
}
</script>
v-model
v-model
は、input
イベントをトリガーにバインドされたデータを更新します。
<input type="text" v-model="val">
これをコンポーネントで実装するためには、2 つのルールがあります。
1 つは、v-model
の値はvalue
というプロパティ名で受け取ります。
props: {
value: {
type: String,
default: '',
validator: (val) => ['blue', 'red'].includes(val)
}
}
もう 1 つは、input
イベントでデータの更新を行います。
changeRed() {
this.$emit('input', 'red')
}
$emit
については後述しますが、これによってバインドされたデータが'red'
に書き変わります。
これを先程のmy-button
に適用すると以下のようになります。
<template>
<button :style="styleObj" @click="changeRed">my-button</button>
</template>
<script>
export default {
props: {
value: {
type: String,
default: '',
validator: (val) => ['blue', 'red'].includes(val)
}
},
computed: {
styleObj() {
return this.value ? { background: this.value } : {}
}
},
methods: {
changeRed() {
this.$emit('input', 'red')
}
}
}
</script>
<template>
<div>
<my-button v-model="color"></my-button>
<button @click="changeColor">change color</button>
</div>
</template>
sync
v-model
と同じく、バインドしたデータを更新する方法にsync
修飾子があります。
<my-button :bg-color.sync="color"></my-button>
v-model
との相違点として、1 つはプロパティ名と数に制限がなくなります。
上記のように、bgColor
というプロパティ名がそのまま使用できます。
またbgColor
以外のプロパティがあれば、そのプロパティにもsync
を付与することができます。
もう 1 つの相違点は、更新方法です。v-model
はinput
イベントでしたが、sync
ではupdate
イベントを使用します。
update
イベントでは、どのプロパティを更新するかを一緒に指定します。
changeRed() {
this.$emit('update:bgColor', 'red')
}
カスタムイベント
例えば以下のようにmy-button
にクリックイベントを設定したとします。
<template>
<my-button :bg-color="color" @click="changeColor"></my-button>
</template>
しかし、これだけではchangeColor
は実行されません。
これを実行させるには、my-button
側でどのように実行するかを明示的に指示する必要があります。
そこで使用するのが$emit
です。まずは以下の例を見てください。
<template>
<button :style="styleObj" @click="onClick">my-button</button>
</template>
<script>
export default {
props: {
bgColor: {
type: String,
default: '',
validator: (val) => ['blue', 'red'].includes(val)
}
},
computed: {
styleObj() {
return this.bgColor ? { background: this.bgColor } : {}
}
},
methods: {
onClick() {
this.$emit('click')
}
}
}
</script>
this.$emit('click')
で、click
にバインドされた処理を実行するという意味になります。
よってchangeColor
が実行されるようになります。
イベント名に制限はなく、例えば以下のようにmy-click
としても動作します。
<my-button @my-click="changeColor"></my-button>
onClick() {
this.$emit('my-button')
}
v-model
やsync
修飾子で用いたinput
とupdate
は、あらかじめ決められたイベントであり、
この命名は避けるようにしてください。
slot
以下のように、my-button
に表示される内容を指定したいとします。
<my-button :bg-color="color" @click="changeColor">
change color
</my-button>
このような時にはslot
を使用します。
slot
には、親で囲んだ要素が反映されます。
<template>
<button :style="styleObj" @click="onClick">
<slot></slot>
</button>
</template>
またslot
で要素を囲むと、親の指定がなかった場合のデフォルト要素になります。
以下の場合は、親の指定がないとmy-button
が表示されるようになります。
<template>
<button :style="styleObj" @click="onClick">
<slot>my-button</slot>
</button>
</template>
ref
ref
は、コンポーネント内の要素を参照するために使用します。
例えば、コンポーネント内の<input>
をボタンのクリックによってフォーカスしたいとします。
<template>
<div>
<input ref="input1" type="text" />
<br />
<button @click="onClick">focus</button>
</div>
</template>
<script>
export default {
methods: {
onClick() {
this.$refs.input1.focus()
},
},
}
</script>
まず参照したい要素にref
属性で参照名を指定します。
ここではinput1
としています。
次に $refs.参照名
で要素を参照します。
あとは要素に対する処理を実行します。
ライフサイクルフック
Vue.js には、ライフサイクルフックといって、Vue インスタンスが生成されてから破棄されるまでのいくつかのタイミングで処理を実行するための関数が用意されています。
ライフサイクルフックには以下のものがあります。詳しくは公式のライフサイクルダイアグラムを参考にしてください。
ライフサイクルフック | 説明 |
---|---|
beforeCreate | インスタンスの初期化時 プロパティオブジェクトにはアクセスできない |
created | インスタンスの初期化後 この時点からプロパティオブジェクトにアクセスできる |
beforeMount | DOM 展開前ref による要素参照はできない |
mounted | DOM 展開後ref による要素参照ができる |
beforeUpdate | DOM 更新前 |
update | DOM 更新後 |
beforeDestroy | インスタンスの破棄前 |
destroyed | インスタンスの破棄後 この時点からプロパティオブジェクトにはアクセスできない |
ライフサイクルフックは、Vue インスタンスだけでなく 1 つのコンポーネントも持ちます。
例えばv-if
によってコンポーネントが DOM に展開された時、beforeCreate
〜mounted
までのライフサイクルフックが実行されます。
コンポーネント内の DOM が更新されればbeforeUpdate
とupdate
が実行されます。
v-if
によってコンポーネントから DOM から消えるとき、beforeDestroy
とdestroyed
が実行されます。
ライフサイクルフックは、必要なものについて以下のように定義します。
export default {
beforeCreate() {
//...
},
created() {
//...
},
beforeMounte() {
//...
},
mounted() {
//...
},
beforeUpdate() {
//...
},
update() {
//...
},
beforeDestroy() {
//...
},
destroyed() {
//...
},
}
例えば、ページ表示時にinput
にフォーカスを当てておくには以下のようにします。
<template>
<div>
<input ref="input1" type="text" />
<br />
<button @click="onClick">focus</button>
</div>
</template>
<script>
export default {
mounted() {
this.$refs.input1.focus()
},
}
</script>