nuxt.js の /pages/
以下に置かれる Vue シングルページコンポーネントは特殊で、 asyncData
や head
などの追加メソッドが呼び出されます。
また、 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>
良い感じに型を使えるようになりました。