146
105

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-02-03

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

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

Suspenseとは?

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

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

Suspenseの書き方

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

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

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

Suspensのサンプル実装

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

Feb-03-2020 21-43-38.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

エラーハンドリング

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

終わりに

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

参考

146
105
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
146
105

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?