この投稿は How fetch works in Nuxt 2.12 (2020/4/6寄稿)の翻訳版です。
Nuxt 2.12で fetch
が利用できるようになりました。この fetch
を利用することでNuxtアプリケーションへの新たなData連携が可能となります。
この記事では、 fetch
が他のライフサイクルhookとどう違って、どう機能するのかを紹介していきます。
FetchとNuxtライフサイクル
Nuxtライフサイクルhookにおいて、 fetchはcreatedの後に実行されます。Vueのライフサイクルhookではthisコンテキストが利用できるわけですが、このfetchでも同様です。
FetchはサーバーサイドでComponentインスタンスが作られた後に呼ばれるので、this
コンテキストがfetch
内で利用できるわけです。
export default {
fetch() {
console.log(this);
}
};
これがpageコンポーネントにおいてどういう意味を持つのかを見てみましょう。
Pageコンポーネント
this
コンテキストが利用できることから、fetchではコンポーネントデータを直接操作することができます。つまり、コンポーネントのローカルデータの取得にVuexのactionやmutationを利用しなくても良いということです。
結果として、Vuexの利用は任意となりますが、利用は可能であるということになります。必要であれば、 this.$store
を使ってVuex storeにアクセスすることは可能です。
fetchの利用範囲
fetchを利用することで事前にdataを非同期取得できますが、これは、/pages
以下のpageコンオポーネントに限らず、/layouts
や /components
配下のあらゆる.vueコンポーネントでfetchの恩恵を受けることができることを意味します。
Layoutコンポーネント
新たに導入された fetch
を利用することでlayoutコンポーネントから直接APIを呼び出すことができるようになりました。これはv2.12より前のバージョンではできなかったことです。
利用例:
- バックエンドからconfigデータを取得し、footerやナビゲーションを動的に生成する
- ナビゲーションに表示するユーザ関連情報の取得(例:ユーザープロフィール、ショッピングカートの品物数)
-
layouts/error.vue
で利用するサイトに関連するデータ
Building-blockコンポーネント(子コンポーネント)
子コンポーネントでもfetch
利用が可能だということは、pageレベルでのデータ取得処理を子コンポーネントに委譲することができるということです。これもv2.12より前のバージョンではできなかったことです。
これにより、ルーティングレベル(route-level)のコンポーネントの負荷を大きく軽減することができます。
利用例:
- これまで通りpropsを子コンポーネントに渡すこともできますが、子コンポーネントに独自のデータ取得ロジックが必要であればそれも可能となります。
複数fetchの実行順
コンポーネントごとにデータfetchロジックを持つことになるので、その実行順については気になるところです。
Fetchはサーバーサイドで一度(Nuxtアプリケーションへの最初のリクエスト時に)呼ばれ、ルーティング処理に応じた呼び出しがクライアントサイドで行われます。しかしながら、各コンポーネントでfetchを実行できるため、これらfetchは階層順で呼び出されることになります。
サーバーサイドfetchの無効化
サーバーサイドのfetch処理を無効化することもできます。
export default {
fetchOnServer: false
};
こうすることで、fetchはクライアントでのみ呼ばれることになります。この fetchOnServer
がfalseに設定されると、コンポーネントがサーバーサイドでレンダリングされる際に、$fetchState.pending
が true
に設定されます。
エラーハンドリング
fetchにおいては、エラーハンドリングはコンポーネントレベルで実行されます。
fetch
では、データが非同期でデータが取得されるため、そのリクエストの完了および成功状態を確認するために $fetchState
オブジェクトが提供されています。
$fetchState = {
pending: true | false,
error: null | {},
timestamp: Integer
};
keyが3つ存在します。
- Pending - クライアントサイドでfetchが呼び出された際のplaceholder表示
- Error - エラーメッセージの表示
- Timestamp - 最後にfetchが呼び出されたtimestamp(keep-aliveを利用したキャッシュで有用)
これらのkeyはコンポーネントのtemplate内で、APIデータ取得中のplaceholderの表示に利用されます。
<template>
<div>
<p v-if="$fetchState.pending">Fetching posts...</p>
<p v-else-if="$fetchState.error">Error while fetching posts</p>
<ul v-else>
…
</ul>
</div>
</template>
メソッドとしての利用
ユーザアクションやコンポーネントメソッドに応じたメソッドとしての利用も可能です。
<!-- from template in template -->
<button @click="$fetch">Refresh Data</button>
// from component methods in script section
export default {
methods: {
refresh() {
this.$fetch();
}
}
};
パフォーマンスを意識したアプローチ
fetch利用おけるNuxt pageコンポーネントのパフォーマンスを良くするため、 :keep-alive-props
propや activated
hookの利用があります。
Nuxtではある一定のpageをデータと一緒にメモリーキャッシュできる仕組みがあります。また、データの再取得までの時間を設定することも可能です。
これを有効にするためには、一般的な <nuxt />
や <nuxt-child />
コンポーネントで keep-alive
propを利用します。
<!-- layouts/default.vue -->
<template>
<div>
<nuxt keep-alive />
</div>
</template>
加えて、 <nuxt />
コンポーネントに :keep-alive-props
を使ってキャッシュするページ数を渡します。
:keep-alive-props
propを設定することで、サイト回遊中にメモリーキャッシュする最大ページ数を指定することができます。
<!-- layouts/default.vue -->
<nuxt keep-alive :keep-alive-props="{ max: 10 }" />
上記は一般的なパフォーマンス改善施策の一例ですが、以下はもっと踏み込んだ例で、$fetchState.timestamp
を利用して、データの再取得までの時間を設定しています。
ここでは、 kee-alive
と Vueの activated
hookを利用します。
export default {
activated() {
// Call fetch again if last fetch more than a minute ago
if (this.$fetchState.timestamp <= Date.now() - 60000) {
this.$fetch();
}
}
};
asyncData vs Fetch
pageコンポーネントに関して言えば、 ローカルデータを扱うという点で、fetch
は asyncData()
と大変似ています。しかし、特筆すべき違いもいくつかあります。
Nuxt 2.12時点で、 asyncData
はまだ利用可能です。では、これらの違いを見ていきましょう。
AsyncData
-
asyncData
はpageレベルのコンポーネントでのみ利用可能 -
this
は利用不可 - dataを返すことでpayloadを渡している
export default {
async asyncData(context) {
const data = await context.$axios.$get(
`https://jsonplaceholder.typicode.com/todos`
);
// `todos` does not have to be declared in data()
return { todos: data.Item };
// `todos` is merged with local data
}
};
Fetch
-
fetch
はすべてのVueコンポーネントで利用可能 -
this
が利用可能 - 単純にローカルデータの編集(mutate)をしている
export default {
data() {
return {
todos: []
};
},
async fetch() {
const { data } = await axios.get(
`https://jsonplaceholder.typicode.com/todos`
);
// `todos` has to be declared in data()
this.todos = data;
}
};
Nuxt 2.11以前のfetch
Nuxtに長らく関わっているのであれば、旧のバージョンの fetch
はかなり違うことに気づくと思います。
これはそんなに大きな変化なのでしょうか
答えはNoです。古い fetch
は依然として第1引数に context
を渡すことができ、既存のNuxtアプリケーションの互換性を維持することができます。
以下に新旧での違いをまとめます。
Point 1
Before - fetch
hook はコンポーネントの初期化前に呼ばれ、 this
は利用できない
After - fetch
は、routeアクセス時、サーバーサイドでのコンポーネントインスタンス生成後に呼ばれる
Point 2
Before - Nuxt contextにはpageレベルコンポーネントでアクセスし、そのcontextは第1引数として渡される
export default {
fetch(context) {
// …
}
};
After - 引数なしにVueクライアントサイド同様に this
contextへのアクセスができる
export default {
fetch() {
console.log(this);
}
};
Point 3
Before - Pageレベルコンポーネント(reoute-level)のみがサーバーサイドデータfetchができる
After - Vueコンポーネントで非同期のpre-fetchができる
Point 4
Before - fetch
はサーバーサイドでは1度(Nuxtアプリへの初回リクエスト時)、クライアントサイドでは、ナビゲーション発生時に呼び出すことができる
After - fetch
は旧バージョンと同じだが...
...コンポーネント単位で fetch
できるため、fetch hookはコンポーネント階層順に呼び出される
Point 5
Before - API呼び出し時のエラーハンドリングとして、context.error
functionを利用してページリダイレクトを実行していた
After - 新しい fetch
では、template内で $fetchState
オブジェクトを利用したエラーハンドリングが可能
ということは、旧バージョンで可能だった、任意のエラーページへの誘導ができないということ??
そうなりまが、 pageレベルコンポーネントの場合 asyncData()
を利用できます。シンプルに this.$nuxt.error({ statusCode: 404, message: Data not found' })
を利用してエラーページへリダイレクトしてください。
最後に
新しい fetch
hookの導入により多くの機能改善や、データfetchおよびrouteレベル、子レベルコンポーネント設計における柔軟性がもたらされました。
これにより、同一routeにおける複数API呼び出しが必要なNuxtプロジェクトの設計においてすこし違う考え方を求められるでしょう。
この記事が新しい fetch
機能の理解の一助となること願っています。