Nuxt2.12.0で新しくなったfetchについて

Nuxt2.12.0でfetch methodが新しくなりました。

https://github.com/nuxt/nuxt.js/releases/tag/v2.12.0

## ...fetch?

っていうか、fetchって使います？ 多分使わないと思います。なぜならasyncDataと機能的にほぼ同じなので。fetchにできることは全部asyncDataで出来ていました。

## 新しくなったというか、ついに定義された真のfetch

で、2.12からは完全に新しくなりました。将来的にはむしろasyncDataを不要にしていくと思います（後述）

APIドキュメント： https://nuxtjs.org/api/pages-fetch

要点としては

- pageComponent限定の機能ではなくなった
- fetch methodの引数にcontextを受け取らなくなったかわりに、thisが使えるようになった（なのでpropを用いたfetchなどが可能に）
- thisに `$fetch`, `$fetchState` が生えた
- `this.$fetch()` で再実行ができるようになった

というあたりです。

## fetch処理中の表示の出し分け

```js
  data() {
    return {
      post: {}
    }
  },
  async fetch() {
    this.post = await this.$http.$get(
      `${this.api}${this.$route.params.id}`
    )
  },
```

fetchでapi叩いてthis.postに入れるとします。

```vue
<template>
  <p v-if="$fetchState.pending">Loading...</p>
  <p v-else-if="$fetchState.error">Error: {{ $fetchState.error.message }}</p>
  <div v-else>
    <h1>{{post.title}}</h1>
    <div>{{post.body}}</div>
  </div>
</template>
```

従来のページコンポーネントでは非同期処理が完了してからページ遷移・表示が行われていましたが、今回新しくコンポーネント単位になったことによりクライアントサイドで実行される場合には処理中の表示が発生します。

そこで 「実行中/エラー/実行完了」 の3つの表示を作るわけですが、$fetchStateにはその状態が自動的に入るので、template側ではこのように表示を出し分けることができます。

## 図

<img width="495" alt="Frame 2.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117743/de86fbd6-9628-5777-f958-f2d10f9adc24.png">

イメージを図にするとこんな感じでしょうか。fetchはmount前（SSR含む）とmethodを叩いたときに呼ばれます。pageComponentではないので、propの値に応じてfetchするようなコンポーネントも作りやすいと思います。

## propの値でfetchする例

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117743/e22a9b23-935a-234a-acf6-5ead3c1051b5.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117743/db773a2b-259a-4957-de7d-0416347b8cab.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/117743/ab015ccd-d9e0-14c8-ffcc-4880830c8f31.png)

https://twitter.com/Atinux/status/1106201847734456320?s=20

古いツイートなので$isFetchingとか変数名が異なってますが、propの値を使ってfetchしてSSRする例です（画像2枚目）。
`fetchOnServer: false` にすることで一部のコンポーネントはクライアント処理のみにするといったこともできます（画像3枚目）

## 仕組み

```js
async function $fetch() {
  this.$nuxt.nbFetching++
  this.$fetchState.pending = true
  this.$fetchState.error = null
  this._hydrated = false
  let error = null
  const startTime = Date.now()

  try {
    await this.$options.fetch.call(this)
  } catch (err) {
    error = normalizeError(err)
  }

  const delayLeft = this._fetchDelay - (Date.now() - startTime)
  if (delayLeft > 0) {
    await new Promise(resolve => setTimeout(resolve, delayLeft))
  }

  this.$fetchState.error = error
  this.$fetchState.pending = false
  this.$fetchState.timestamp = Date.now()

  this.$nextTick(() => this.$nuxt.nbFetching--)
}
```
https://github.com/nuxt/nuxt.js/blob/1f3c3ad9f3f0da93e1fb59aa0bed4be768f2f1fe/packages/vue-app/template/mixins/fetch.client.js#L55-L79


この辺の処理は上記のコードにある通り、fetchメソッドの実行が完了するまでは `$fetchState.pending` がtrueになり、fetchメソッドでエラー発生した場合には `$fetchState.error` にエラーオブジェクトが入るという形になっています。

つまり、fetchメソッドの中では敢えてtry catchせずエラーをそのまま投げるように書くことになります。

## asyncDataとの違い

これでやっとasyncDataと住み分けができてきた感じです。

現状でfetchに無いのは [context](https://nuxtjs.org/api/context/) です。以前のfetchには引数に渡されていましたが今回の変更で無くなりました。

redirectやresponseのstatusCodeを設定するにはcontextが必要ですが、fetchでは使えないのでしょうか。現状では使えません。そういう処理のためには引き続きasyncDataが必要です。

[Nuxt 3: Introducing fetch() hook](https://github.com/nuxt/rfcs/issues/27)

> Context
fetch hook does not receive any context as 1st argument anymore since it has access to this.

> The context will be updated to be available through this.\\$ctx, this.\\$config and this.\\$nuxt, learn more on #25

[Nuxt 3: Context changes](https://github.com/nuxt/rfcs/issues/25)

ですがRFCにあるように将来的には this.$ctx が生えるようになるようです。
こちらを使うことでasyncDataの役割はfetchでできるようになっていくと思います。

## 追記: context使えます

https://twitter.com/Atinux/status/1240193656969101312

context、`this.$nuxt.context` で使えました。

- redirectは `this.$nuxt.context.redirect(path)` 
- resは `process.server` がtrueのときに `this.$nuxt.context.res` に入るので設定できる感じです