LoginSignup
17
14

More than 3 years have passed since last update.

nuxt.js + TypeScript でうまくページコンポーネントを定義する

Last updated at Posted at 2019-07-31

nuxt.js の /pages/ 以下に置かれる Vue シングルページコンポーネントは特殊で、 asyncDatahead などの追加メソッドが呼び出されます。

また、 plugin で値を inject して拡張することも出来ます。

そのあたりを考慮しつつ TypeScript で上手に型定義が出来ると嬉しいですね。

ということで今使っている型定義を共有してみます。

ここでは nuxt-property-decorator を使って記述していますが、 vue-class-component だけでも動作します(Component しか使っていないので)。

型ファイル

d.ts 型定義ファイルは、 inject する Foo クラスの型も定義しないといけなくて面倒なので使いません。

src/vue.ts
import { Component, Vue } from 'nuxt-property-decorator'
// 次のバージョンでは @nuxt/typescript の方に型定義が移動する予定です
import { Context, NuxtAppOptions } from '@nuxt/vue-app'
import { Foo } from '~/src/foo'

/**
 * プラグインで `inject` した値
 */
export interface InjectedValues {
  $foo: () => Foo
}

/**
 * nuxt の Context に `inject` した値を追加した型
 */
export interface NuxtAppContext extends Context {
  app: NuxtAppOptions & InjectedValues
}

/**
 * 一番基底となるコンポーネントクラス
 */
@Component
export class BaseVue extends Vue implements InjectedValues {
  // 特に実装はなし
}

/**
 * ページ以外のコンポーネントの基底クラス
 */
@Component
export class ComponentVue extends BaseVue {
  /**
   * head は PageComponentVue のみ定義出来る
   */
  public head!: void

  /**
   * asyncData は PageComponentVue のみ定義出来る
   */
  public asyncData!: void

  /**
   * fetch は PageComponentVue のみ定義出来る
   */
  public fetch!: void
}

/**
 * ページコンポーネントの基底クラス
 */
@Component
export class PageComponentVue extends BaseVue {
  /**
   * meta タグを埋める
   */
  public head(): any {
    // ページタイトルを必須にするため、デフォルトはエラー
    throw new Error(`head メソッドを定義してください`);
  }

  public mounted() {
    // ここではクライアントレンダリング時に全てのページで行う初期化を記述する
  }


  /**
   * ページをレンダリングする前に呼ばれる。戻り値を object にすることで data に値を詰めれる。
   * @param this **この関数内で this は利用出来ない**
   * @param ctx コンテキスト
   */
  public async asyncData(this: void, ctx: NuxtAppContext): Promise<any> {
    // do nothing
  }

  /**
   * ページをレンダリングする前に呼ばれる。
   * @param this **この関数内で this は利用出来ない**
   * @param ctx コンテキスト
   */
  public async fetch(this: void, ctx: NuxtAppContext): Promise<void> {
    // do nothing
  }
}

/**
 * `/layouts` 以下のコンポーネントの基底クラス
 */
@Component
export class LayoutComponentVue extends ComponentVue {
  // 特に実装はなし
}

ページコンポーネントファイル

pages/index.vue
<template>
  <h1>Index</h1>
</template>

<script lang="ts">
import { Component } from 'nuxt-property-decorator'
import { PageComponentVue, NuxtAppContext } from '~/src/vue'

@Component({
  layout: 'simple',
  // サブコンポーネントがあればここで定義
  // components: { SubComponent, },
  // middleware もここで定義
  // middleware: 'guest',
})
export default class IndexPage extends PageComponentVue {
  public head() {
    return {
      title: 'インデックスページ',
      meta: [
        { hid: 'og:title', name: 'og:title', content: 'インデックスページ' },
      ],
    }
  }

  public async asyncData(this: void, ctx: NuxtAppContext): Promise<any> {
    // this は利用出来ないことが明示される
    // inject した値が利用出来る
    ctx.app.$foo()
  }
}
</script>

普通のコンポーネントファイル

components/the-title.vue
<template>
  <h1>{{ title }}</h1>
</template>

<script lang="ts">
import { Component, Prop } from 'nuxt-property-decorator'
import { ComponentVue } from '~/src/vue'

@Component
export default class TheTitle extends ComponentVue {
  @Prop({
    type: String,
    required: true,
  })
  public readonly title!: string
}
</script>

良い感じに型を使えるようになりました。

17
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
14