小さなエンドウ豆

まだまだいろいろ勉強中

nuxt-property-decorator を使って Vue コンポネントをクラス構文に対応させる

nuxt-property-decorator を使って Vue コンポネントをクラス構文に対応させる

Nuxt + TypeScript のプロジェクトを考える際にあると便利な nuxt-property-decorator について調べたのでまとめる。
とりあえず使ってみた感想はクラス構文にすることによって簡潔に書くことができるのが良い点だと思った。

用語の説明や使い方などを記していく。

デコレーターとは

TypeScript で注釈(またはアノテーション)を class や method に付与することのできる宣言のことである。
Vue(Nuxt)の場合、クラスやメソッドに付与することにより、 コンポネントであることや、メソッドが Getter や Action を示すなどの視覚的な情報や そのアノテーション独自の機能を対象に付与することができる。

以下が公式の定義である(英語)

www.typescriptlang.org

日本語もあるよ。

js.studio-kingdom.com

nuxt-class-decorator とは

Nuxt プロジェクトにデコレータを持ち込むためのライブラリである。 もともとの vue-class-decorator という Vue + TypeScript でクラス構文を可能にするライブラリの派生らしい。

そのためシンタックスは Vue のクラス構文になる。

create-nuxt-app をしたあとの pages/index.vue の内容をクラス構文に変えてみる。

before

<template>
  <div class="container">
    <div>
      <logo />
      <h1 class="title">
        {{ title }}
      </h1>
      <h2 class="subtitle">
       {{ description }}
      </h2>
      <div class="links">
        <a
          href="https://nuxtjs.org/"
          target="_blank"
          class="button--green"
        >
          Documentation
        </a>
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          class="button--grey"
        >
          GitHub
        </a>
      </div>
    </div>
  </div>
</template>

<script>
import Logo from '~/components/Logo.vue'
export default {
  components: {
    Logo
  },
  data(): {
    return { title: 'test app' }
  },
  computed: {
    description: { `${title} is a My app` }
  }
}
</script>

after

<template>
  <div class="container">
    <div>
      <logo />
      <h1 class="title">
        {{ title }}
      </h1>
      <h2 class="subtitle">
        {{ subtitle }}
      </h2>
      <div class="links">
        <a
          href="https://nuxtjs.org/"
          target="_blank"
          class="button--green"
        >
          Documentation
        </a>
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          class="button--grey"
        >
          GitHub
        </a>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import Logo from '~/components/Logo.vue'

@Component({
  components: {
    Logo
  }
})
export default class Index extends Vue {
  public title: string = 'test app'
  public subtitle: string = `${this.title} is My app`
}
</script>

クラス構文に書き直すとこのように変わる。
ここで登場するのが @Component というアノテーションです。 これが「このクラスが Vue コンポーネントですよー」と宣言している。 Component にはいろいろなオプションを与えることができる。(今回だと components がそれに当たる。) アノテーションを付与することによって内部的にはメタプロしているみたいだけどどのようなことしているのかまではわかっていない。

また Vue コンポーネント特有のメソッド data や computed がクラスのインスタンス変数みたいに定義することができる。

次に子コンポネント(components/Child.vue)を新たに追加してみる。

# components/Child.vue
<template>
  <div class="child">
    <p>{{ child.name }}</p>
  </div>
</template>

<script lang="ts">
import { Prop, Component, Vue } from "nuxt-property-decorator";
import ChildType from "../models/child"

@Component
export default class Child extends Vue {
  @Prop()
  child!: ChildType
}
</script>

# pages/index.vue
<template>
  <div class="container">
    <div>
      <logo />
      <h1 class="title">
        {{ title }}
      </h1>
      <h2 class="subtitle">
        {{ subtitle }}
      </h2>
      <div v-for="child in childs" :key="child.id">
        <Child :child="child"/>
      </div>
      <div class="links">
        <a
          href="https://nuxtjs.org/"
          target="_blank"
          class="button--green"
        >
          Documentation
        </a>
        <a
          href="https://github.com/nuxt/nuxt.js"
          target="_blank"
          class="button--grey"
        >
          GitHub
        </a>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
import Logo from '~/components/Logo.vue'
import Child from '~/components/Child.vue'
import { store } from '~/store'

@Component({
  components: {
    Logo,
    Child
  }
})
export default class Index extends Vue {
  public title: string = 'iVoteFront'
  public subtitle: string = `${this.title} is My app`

  get childs() {
    return store.childs
  }
  async asyncData() {
    await store.loadDatas()
  }
}
</script>

コンポーネントの属性には @Prop というアノテーションを付与する。 また Props に Type を指定することでどのような値が親コンポーネントから渡ってくるかがわかりやすい。

ここからは余談ですが、 親コンポーネントでは store から子コンポーネントにわたすデータを取得する例で書いている。

メソッドの前に get というキーワードを書くことによってそのメソッドが算出プロパティ( Vue で言うところの computed)であることを表す。

asyncData は Nuxt 特有の機能で SSR 途中などに外部の API などからデータを得てレンダリングしたい場合などに使われる。 今回の例だと loadData() で childs という state に値をセットしている処理が書かれている。

ここらへんの話は vuex-module-decorator の回でまとめる。

まとめ

デコレータとはなにか nuxt-property-decorator の使い方がわかった。

注意が必要なのはデコレータの機能は実験的らしくこれが今後スタンダードになっていくかがまだわからないということ。

まだ使ったことない注釈もたくさんあり場合によっては便利なシチュエーションがあると思うので今後を追っていきたい。