nuxt.svg

【Nuxt 2】コンポーネントの基礎

Nuxt2

コンポーネントとは

コンポーネントは、直訳すると構成要素や部品のことです。 WEB の構成要素といえば、ヘッダー、フッター、サイドバーなど、さらに細かい単位でボタン、テキストフィールドなどが考えられます。

NuxtJS では、コンポーネントを 1 つの vue ファイル(単一ファイルコンポーネント)として作成します。 そしてこれらを組み合わせることでページを構成します。

ページやレイアウトも単一ファイルコンポーネントで作成します。 ページ、レイアウト以外のコンポーネントは/componentsフォルダに作成します。

コンポーネントの関係性は、よく親子関係で表現されます。 コンポーネント A からコンポーネント B を使用する場合、A が親、B が子というように表現します。

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

単一ファイルコンポーネントは、1 つのコンポーネントに対する HTML、JavaScript、CSS を定義します。

Vue
<template>
  <!-- HTML部分 -->
</template>

<script>
export default {
  //JavaScript(Vue.js)部分
}
</script>

<style scoped>
  /* CSS部分 */
</style>

template

templateには、コンポーネントの HTML 要素を定義します。 注意点として、必ずルート要素が 1 つとなるようにします。

OK
<template>
  <div>
    <p>{{ counter }}, {{ doubleCounter }}</p>
    <button @click="count">count up</button>
  </div>
</temaplte>
NG
<template>
  <p class="count-text">{{ counter }}, {{ doubleCounter }}</p>
  <button @click="count">count up</button>
</temaplte>

script

scriptには、JavaScript、主に Vue.js の定義をします。 export default内に、datamethodscomputedなどオプションオブジェクトの内容を定義します。 この定義については、通常の 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に以下の設定を行います。

nuxt.config.js
export default {
  components: [
    {
      path: '@/components/',
      pathPrefix: false
    }
  ]
}

プロパティ

プロパティとは

コンポーネントでは、オプションオブジェクトにプロパティ(props)を定義することができます。

以下の例を見てください。

MyButton.vue
<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>
Page
<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プロパティのデータ型(StringNumberBooleanArrayObject
defaultプロパティの指定がなかった時のデフォルト値
requiredプロパティの指定が必須か(true or false
validationプロパティのバリデーション関数

bgColorでは、String型、指定がない場合は''が設定され、かつ'blur''red'以外の値が指定された場合はエラーとなります。

プロパティへのアクセスは、dataと同じくthisを付けます。

プロパティの指定は、bg-color="blue"のようにケバブケースを用います。 これによって、コンポーネントのbgColor'blue'が設定されます。

プロパティはデータバインドすることで動的に変化させることができます。

Page
<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イベントをトリガーにバインドされたデータを更新します。

Page
<input type="text" v-model="val">

これをコンポーネントで実装するためには、2 つのルールがあります。

1 つは、v-modelの値はvalueというプロパティ名で受け取ります。

MyButton.vue
props: {
  value: {
    type: String,
    default: '',
    validator: (val) => ['blue', 'red'].includes(val)
  }
}

もう 1 つは、inputイベントでデータの更新を行います。

MyButton.vue
changeRed() {
  this.$emit('input', 'red')
}

$emitについては後述しますが、これによってバインドされたデータが'red'に書き変わります。

これを先程のmy-buttonに適用すると以下のようになります。

MyButton.vue
<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>
Page
<template>
  <div>
    <my-button v-model="color"></my-button>
    <button @click="changeColor">change color</button>
  </div>
</template>


sync

v-modelと同じく、バインドしたデータを更新する方法にsync修飾子があります。

Page
<my-button :bg-color.sync="color"></my-button>

v-modelとの相違点として、1 つはプロパティ名と数に制限がなくなります。 上記のように、bgColorというプロパティ名がそのまま使用できます。 またbgColor以外のプロパティがあれば、そのプロパティにもsyncを付与することができます。

もう 1 つの相違点は、更新方法です。v-modelinputイベントでしたが、syncではupdateイベントを使用します。 updateイベントでは、どのプロパティを更新するかを一緒に指定します。

Page
changeRed() {
  this.$emit('update:bgColor', 'red')
}

カスタムイベント

例えば以下のようにmy-buttonにクリックイベントを設定したとします。

Page
<template>
  <my-button :bg-color="color" @click="changeColor"></my-button>
</template>

しかし、これだけではchangeColorは実行されません。 これを実行させるには、my-button側でどのように実行するかを明示的に指示する必要があります。

そこで使用するのが$emitです。まずは以下の例を見てください。

MyButton.vue
<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としても動作します。

Page
<my-button @my-click="changeColor"></my-button>
MyButton.vue
onClick() {
  this.$emit('my-button')
}

v-modelsync修飾子で用いたinputupdateは、あらかじめ決められたイベントであり、 この命名は避けるようにしてください。

slot

以下のように、my-buttonに表示される内容を指定したいとします。

Page
<my-button :bg-color="color" @click="changeColor">
  change color
</my-button>

このような時にはslotを使用します。 slotには、親で囲んだ要素が反映されます。

MyButton.vue
<template>
  <button :style="styleObj" @click="onClick">
    <slot></slot>
  </button>
</template>

またslotで要素を囲むと、親の指定がなかった場合のデフォルト要素になります。 以下の場合は、親の指定がないとmy-buttonが表示されるようになります。

MyButton.vue
<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インスタンスの初期化後
この時点からプロパティオブジェクトにアクセスできる
beforeMountDOM 展開前
refによる要素参照はできない
mountedDOM 展開後
refによる要素参照ができる
beforeUpdateDOM 更新前
updateDOM 更新後
beforeDestroyインスタンスの破棄前
destroyedインスタンスの破棄後
この時点からプロパティオブジェクトにはアクセスできない

ライフサイクルフックは、Vue インスタンスだけでなく 1 つのコンポーネントも持ちます。

例えばv-ifによってコンポーネントが DOM に展開された時、beforeCreatemountedまでのライフサイクルフックが実行されます。 コンポーネント内の DOM が更新されればbeforeUpdateupdateが実行されます。 v-ifによってコンポーネントから DOM から消えるとき、beforeDestroydestroyedが実行されます。

ライフサイクルフックは、必要なものについて以下のように定義します。

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>