Vue.js 3.0 の新機能を試す。 〜 Suspense 編〜 

2020年Q1リリース予定のVue.js 3.0の新機能 `Suspense` を試してみたのでまとめます。
かなり使い勝手が良さそうです。

（参考）
以下でVue 3.0（vue-next）の環境構築、他の新機能についてもまとめています。

* [vue\-next（Vue\.js 3\.0 wip）\+ TypeScript \+ webpackでの開発環境を構築する](https://qiita.com/ryo2132/items/2b10f03e3d4ea04be649)
* [Vue.js 3.0 の新機能を試す。 〜 Teleport 編〜](https://qiita.com/ryo2132/items/a620755b04294ffabde6)

# Suspenseとは？

非同期処理が解決されるまでフォールバックコンテンツ（例えばLoading中アイコン）を表示してくれる特別なコンポーネントです。
いままで、`v-if="loading === true"`などの状態変数を使って制御していたものを、状態変数を使わずに簡潔に書くことができます。

※ Reactには既にあるようです。https://reactjs.org/docs/concurrent-mode-suspense.html
※ [@kazuponさんの資料](https://speakerdeck.com/kazupon/mamonakuyatutekuru-vue-dot-js-3)によると、まだビルドオプションで制御される機能で、不確定な部分もあるようです。

# Suspenseの書き方
以下のように、`<Suspense>`コンポーネントでラップして、内部の`<template #default>`内に、非同期コンポーネントを、`<template #fallback>`内に、その非同期コンポーネントが解決するまでに描画したいもの（フォールバックコンテンツ）を記載します。
それだけで、非同期コンポーネントの処理が解決するまでフォールバックコンテンツを描画してくれるようになります。

この場合だと、`<AsyncComponents>`の非同期処理が終わるまで、`loading...`という文字列を表示してくれます。

```vue
  <Suspense>
    <template #default>
      <AsyncComponents/>
    </template>
    <template #fallback>
      loading...
    </template>
  </Suspense>
```




# Suspensのサンプル実装

サンプルとして、このGIFのように非同期処理待ちの際にLoading表示をするコンポーネントを作ります。

![Feb-03-2020 21-43-38.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/4bddcec2-5917-21c4-b2f8-46e22f0ad9b9.gif)

以下で紹介しているコードはこちらにあります。
https://github.com/kawamataryo/vue-next-ts-webpack-preview/tree/suspense-sample


## 1. 非同期コンポーネントの作成

まず、非同期処理を行うコンポーネントを作ります。
UserデータをAPIから取得して、User名のリストを表示することを想定しています。

ポイントは**setup()関数でasync/awaitを使いPromiseを返すところ**です。
これで非同期コンポーネントとして、Suspenseでの待機対象となります。

```src/components/AsyncUsers.vue
<template>
  <ul>
    <li v-for="(user, i) in users" :key="i">{{ user }}</li>
  </ul>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  async setup() {
    // APIリクエストのモック 2000ms後にユーザー名の配列を返す
    const fetchUsers = () => {
      return new Promise<string[]>(resolve => {
        setTimeout(() => {
          resolve(['Jon', 'Bob', 'Nancy'])
        }, 2000)
      })
    };

　　 // ユーザー名を取得
    // setup()内で読んでいるのでVue.js 2系で言うonCreated()と同じライフサイクルで実施される。
    const users = await fetchUsers();

    return {
      users
    }
  }
});
</script>
```

## 2. Suspense 利用側のコンポーネント

次に、Suspense利用側のコンポーネントを書いていきます。
非同期コンポーネントを`<Suspense>`の`<template #default>`内に記載して、
`<template #fallback>`内に非同期処理が解決するまで描画したい`Loading...`を記載しています。

これだけで非同期処理を待つ間はLoadingを表示するという実装が出来ます。


```src/App.vue
<template>
  <img src="./logo.png">
  <h1>Suspense demo</h1>
  <Suspense>
    <template #default>
      <AsyncUsers/>
    </template>
    <template #fallback>
      Loading...
    </template>
  </Suspense>
</template>

<script lang="ts">
import { ref, defineComponent, onErrorCaptured, Ref } from 'vue'
import AsyncUsers from "./components/AsyncUsers.vue"

export default defineComponent({
  components: {
    AsyncUsers
  },
  setup() {}
})
</script>
```

# 複数の非同期コンポーネントを待つ場合

Suspenseが特に効果を発揮するのが、複数の非同期コンポーネントを待つ場合だと思います。
実装はとても簡単です。`<Suspense>`内に処理を待ちたい非同期コンポーネントを列挙すればOKです。
全ての非同期コンポーネントが解決するまで、フォールバックコンテンツを描画してくれます。


```src/App.vue
    <Suspense>
      <template #default>
        <AsyncUsers/>
        <AsyncFoods/>
        <AsyncAnimals/>
      </template>
      <template #fallback>
        Loading...
      </template>
    </Suspense>
```

以下のサンプルでは、AsyncUsers, AsyncAnimalsが2000ms、AsyncFoodsが3000msの描画待ちで設定しています。
全てが解決するまでloadigを表示してくれてます。

![Feb-03-2020 22-06-56.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/3bbe45cb-92ee-da23-a5fe-019906d3d3da.gif)

# エラーハンドリング
APIリクエストといったらエラーハンドリングが必要ですよね。
`Suspense`を使う場合のエラーハンドリングは、Vue3.0で新しく追加されたライフサイクルフック`onErrorCaptured`で簡潔にかけます。

以下は`AsyncUsers`で発生したエラーを`onErrorCaptured`でキャッチして、エラーメッセージを表示する例です。

```src/App.vue
<template>
  <div class="container">
    <img src="./logo.png">
    <h1>Suspense demo</h1>
    <div  v-if="error">
        {{ error }}
    </div>
    <Suspense>
      <template #default>
        <AsyncUsers>
      </template>
      <template #fallback>
        Loading...
      </template>
    </Suspense>
  </div>
</template>

<script lang="ts">
import {defineComponent, onErrorCaptured, ref, Ref} from 'vue'
import AsyncUsers from "./components/AsyncUsers.vue"

export default defineComponent({
  components: {
    AsyncUsers,
  },
  setup() {
    const error: Ref<any> = ref(null);

    // 非同期コンポーネントでErrorが発生した場合、このライフサイクルフックでキャッチされる。
    // エラーの内容を、リアクティブなエラー表示用変数の`error`に詰めている
    onErrorCaptured(e => {
      error.value = e;
      return true;
    });

    return {
      error
    }
  }
})
</script>

```

`AsyncUsers`であえてreject(500 Server Error")を発生させた場合はこのようになります。

![Feb-04-2020 11-28-22.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/fef70ed0-7807-fef3-ebf8-838357587230.gif)



# 終わりに
以上、Vue.js 3.0 の新機能を試す。 〜Suspense編〜 でした
状態変数を持たなくて良いのでとてもシンプルにかけますね。
早く使いたい..!!


# 参考

- https://speakerdeck.com/kazupon/mamonakuyatutekuru-vue-dot-js-3
- https://vueschool.io/articles/vuejs-tutorials/suspense-new-feature-in-vue-3/