今回達成すること
ブログ記事を表示するコンテンツページを作成します。
その中で、NuxtChildコンポーネントについても解説します。
ブログ記事コンテンツページのファイルを作成する
今回作成するブログ記事コンテンツページのパスは以下の通りです。
- パス
/posts/<category.fields.slug>/<post.fields.slug>
このパスに沿ったファイル構成は以下のようになります。
pages
└── posts
└── _category
└── _slug.vue
上記のディレクトリとページファイルを作成しましょう。
% mkdir -p pages/posts/_category && touch $_/_slug.vue
NuxtChildコンポーネントとは?
今回はNuxtChildコンポーネントを使用して、
NuxtChildコンポーネントとは、ネストされたルートの子コンポーネントを表示するために使用します。
pages
└── posts
└── _category
└── _slug.vue => ネストされたルートの子コンポーネント
- ネストされたルートの、
- 親ディレクトリと同じ名前のページファイルを作成することで、
- 親コンポーネントとして扱うことができます。
pages
└── posts
├── _category
│ └── _slug.vue => ネストされたルートの子コンポーネント
└── _category.vue => ネストされたルートの親コンポーネント(親ディレクトリと同じ名前のページファイル)
子コンポーネントの呼び出しは、
- 親の
_category.vue から - NuxtChildコンポーネントを使用します。
NuxtChildの呼び出し場所には、
pages/posts/_category.vue
<template>
<div>
<!-- ここに_slug.vueのコンテンツが表示される -->
<nuxt-child />
</div>
</template>
pages/posts/_category/_slug.vue
<template>
<div>
hogehoge
</div>
</template>
結果
親コンポーネント_category.vueを作成する
今回、親コンポーネントの
% touch pages/posts/_category.vue
pagesディレクトリはこのようになりました。
pages
├── categories
│ └── _slug.vue
├── index.vue
└── posts
├── _category
│ └── _slug.vue
└── _category.vue
ただ、カテゴリーを取得するパラメーターはparams.category
を参照します。
pages/posts/_category.vue
<template>
<v-container>
<nuxt-link
to="/"
>
Home
</nuxt-link>
<div>
Category title: {{ category.fields.title }}
</div>
<!-- ここのブログ記事が表示される -->
<nuxt-child
:category="category"
/>
<h2>
関連記事
</h2>
<ul>
<li
v-for="(post, i) in categoryPosts"
:key="`post-${i}`"
>
[{{ post.fields.category.fields.title }}]
{{ post.fields.title }}
</li>
</ul>
</v-container>
</template>
<script lang="ts">
import { Context } from '@nuxt/types'
import { Component, Vue } from 'nuxt-property-decorator'
import { BlogCategory, BlogPost } from '~/store/types'
type AsyncData = void | {
category: BlogCategory
categoryPosts: BlogPost[]
}
@Component
export default class PostsCategoryPage extends Vue {
validate ({ params }: Context): boolean {
return !!params.category
}
asyncData ({ app: { $accessor }, params, $my }: Context): AsyncData {
const category: BlogCategory | undefined =
$accessor.categories.find((category: BlogCategory) =>
/* params.categoryから検索する */
category.fields.slug === params.category
)
if (!category) {
return $my.errorHandler(404)
}
const categoryPosts: BlogPost[] = $accessor.categoryPosts[category.sys.id]
return {
category,
categoryPosts
}
}
}
</script>
カテゴリー検索メソッドをVuexに用意する
$accessor.categories.find()
の処理は、他のページでも使用されています。
そこで、Vuexのgetter
にslug
から現在のカテゴリーを返す関数を追加します。
store/index.ts
export const getters = getterTree(state, {
// 引数のslugから現在のパスと一致するカテゴリーを返す
currentCategoryFor: (state: RootState) => (slug: string | undefined) => {
if (!slug) { return undefined }
return state.categories.find(category =>
category.fields.slug === slug
)
}
})
検索処理をVuexのメソッドに書き換える
pages/posts/_category.vue
とpages/categories/_slug.vue
の
$accessor.categories.find()
の処理を書き換えましょう。
pages/posts/_category.vue
asyncData ({ app: { $accessor }, params, $my }: Context): AsyncData {
const category: BlogCategory | undefined =
// 書き換え
$accessor.currentCategoryFor(params.category)
// $accessor.categories.find((category: BlogCategory) =>
// category.fields.slug === params.category
// )
...
}
pages/categories/_slug.vue
asyncData ({ app: { $accessor }, params, $my }: Context): AsyncData {
const category: BlogCategory | undefined =
// 書き換え
$accessor.currentCategoryFor(params.slug)
// $accessor.categories.find((category: BlogCategory) =>
// category.fields.slug === params.slug
// )
...
}
ブログ記事コンテンツページを作成する
ブログ記事を表示する
props
でcategory
を受け取り、asyncData()
ではパラメーターと一致するブログ記事を取得します。
pages/posts/_category/_slug.vue
<template>
<div>
Post title: {{ post.fields.title }}
</div>
</template>
<script lang="ts">
import { Context } from '@nuxt/types'
import { Component, Vue, Prop } from 'nuxt-property-decorator'
import { BlogCategory, BlogPost } from '~/store/types'
type AsyncData = void | {
post: BlogPost
}
@Component
export default class PostsCategorySlugPage extends Vue {
@Prop({
type: Object,
required: true
})
category!: BlogCategory
validate ({ params }: Context): boolean {
return !!params.slug
}
asyncData ({ app: { $accessor }, params, $my }: Context): AsyncData {
const post: BlogPost | undefined =
$accessor.posts.find((post: BlogPost) =>
post.fields.slug === params.slug
)
if (!post) {
return $my.errorHandler(404)
}
return {
post
}
}
}
</script>
@Prop()
...nuxt-property-decorator
を使用した場合のprops
の書き方。@Prop({ required: true })
...required: true
の場合は、必ずprops
の値を渡す必要がある。これは、props
の値が親コンポーネントで確定している場合に使用する。category!
... 初期値を代入する必要がない場合は、!
をつけて変数を宣言する。
リンク元をオブジェクトに書き換える
<nuxt-link to="">
を使用している場合も、渡すオブジェクトは同じように書き換えてください。
<!--
記事コンテンツページへのリンクを下記に書き換え
{ name: 'posts-category-slug', params: { category: post.fields.category.fields.slug, slug: post.fields.slug }
-->
<v-list-item
v-for="(post, i) in posts"
:key="`post-${i}`"
@click="$router.push({ name: 'posts-category-slug', params: { category: post.fields.category.fields.slug, slug: post.fields.slug }})"
>
</v-list-item>
ブログ記事コンテンツページを表示しよう
ブラウザで記事のコンテンツページへアクセスしてみましょう。
- 赤枠で囲んだ部分が
<nuxt-child />
タグが表示しているコンテンツ、 - 黄色で囲んだ部分が
_category.vue に用意したコンテンツが表示されています。
404が返ってきているか確認しよう
下記4つのパターンも確認し、404エラーが返ってきているか確認してください。
- 存在しない
params.slug
の場合 params.slug
のパスがない場合- 存在しない
params.category
の場合 params.category
のパスがない場合
コンテンツがスクロールされている問題に対応する
一度ホームに戻って、別のコンテンツも表示してみます。
するとスクロール位置が記憶されていて、ページ末尾のコンテンツが表示されました。
Nxutの子ルートはスクロール位置を記憶する
これはNuxtのデフォルトの仕様で、子ルートを使用するとスクロール位置を記憶します。
これを回避し、子ルートのスクロール位置を一番上にする場合はscrollToTop
プロパティにtrue
を渡します。
export default {
scrollToTop: true
}
scrollToTop()を宣言して対応する
nuxt-property-decorator
では、プロパティを直接クラス内に宣言すると、データの宣言とみなされるため、今回は関数で宣言します。
scrollToTop()
を追加します。
pages/posts/_category.vue
@Component
export default class PostsCategoryPage extends Vue {
...
// 追加
// Doc: https://nuxtjs.org/docs/components-glossary/scrolltotop
scrollToTop (): boolean {
return true
}
}
これで子ルートでもスクロール位置を記憶しなくなりました。
今回の作業は以上です。
% git add -A
% git commit -m "Add post content page"
まとめと次回
今回はNuxtChildコンポーネントを使って、ブログ記事のコンテンツページを作成しました。
次回はタグの一覧ページを作成します。