Upgrade to Pro — share decks privately, control downloads, hide ads and more …

段階的なレンダリングを実装してUXを最大化するためのTips

Rei Sugiura
October 19, 2024
85

 段階的なレンダリングを実装してUXを最大化するためのTips

VueFes2024での登壇資料です。

Rei Sugiura

October 19, 2024
Tweet

Transcript

  1. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. 段階的なレンダリングを実装してUXを最大化するためのTips 
 ソフトウェアエンジニア@株式会社GROWTH VERSE
 杉浦伶/Rachel

  2. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. ソフトウェアエンジニア@株式会社GROWTH VERSE
 
 Github: @reibomaru
 X: @rachel_camper
 自己紹介 杉浦伶/Rachel

  3. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. TL;DR
 • 段階的にコンテンツを表示する実装でLoading Stateの制御が複雑になる場 合にSuspenseが有効 • Tip1: Imgタグでの画像のフェッチをSuspenseで制御するためにBlobを利用 する • Tip2: フェッチのwaterfallを防ぐために、Promiseをpropsで受け渡す
  4. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. アジェンダ・話さないこと アジェンダ • 題材にするダッシュボードの説明 • Suspenseを使った実装のTips a. 画像フェッチの制御 b. waterfallの解決 話さないこと • Suspenseの使い方の説明 • Suspense以外の段階表示の実装 • 初期表示以外でのLoading State UIの 実装 • onErrorCaptured()等でのエラーハンドリ ング • Nuxt.jsを使ったSSRと組み合わせた段階 的レンダリング
  5. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Suspense(experimental) • Suspenseのdefault slotにある全てのコン ポーネントのsetup関数のPromiseが解決 するまでfallback slotを表示する ◦ ex: 全てのUserCardContainerの awaitが解決するまでスケルトンが表 示される • isLoadingのような状態を持つ必要がなくな る。段階的なレンダリング等で制御が複雑 になる場合に有効 • 実験的な機能 であるため、仕様が今後変 更になる可能性もある <template> <transition mode="out-in"> <suspense> <div class="card-wrapper"> <user-card-container v-for="user in users" :key="user.id" :user="user" /> </div> <template #fallback> <div class="card-wrapper"> <div v-for="user in users" :key="user.id"> <skeleton-user-card /> </div> </div> </template> </suspense> </transition> </template> <script setup lang="ts"> import UserCardContainer from "../containers/UserCardContainer.vue"; import SkeletonUserCard from "../components/SkeletonUserCard.vue"; import { useUserAsync } from "../composables/useExternalData"; const users = await useUserAsync(); </script>
  6. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. 題材: ダッシュボード 要件 • データのフェッチを待機している コンポーネントはスケルトンを表 示 • アイコン画像は全ての読み込み が終わったタイミングで一斉にス ケルトンを解除 • 各カードのチャートはフェッチが 完了したものから順次表示
  7. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. 題材: ダッシュボード 要件 • データのフェッチを待機している コンポーネントはスケルトンを表 示 • アイコン画像は全ての読み込み が終わったタイミングで一斉にス ケルトンを解除 • 各カードのチャートはフェッチが 完了したものから順次表示
  8. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. App UserCardList コンポーネント設計 UserCardContainer UserCard ScoreChart Skeleton UserCard Skeleton UserCard Skeleton ScoreChart UserCardContainer UserCard ScoreChart Skeleton ScoreChart Suspense Boundary
  9. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Tip1: 画像の読み込み待機をSuspenseで管理する

  10. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Tip1: 画像の読み込み待機を Suspenseで管理する 要件 • データのフェッチを待機している コンポーネントはスケルトンを表 示 • アイコン画像は全ての読み込み が終わったタイミングで一斉に スケルトンを解除 → 画像のフェッチをSuspense の対象にしたい • チャートはフェッチが完了したも のから順次表示
  11. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Tip1: 画像の読み込み待機を Suspenseで管理する App UserCardList: ユーザー一覧フェッチ UserCardContainer: Avatarのフェッチ Scoreデータのフェッチ UserCard ScoreChart UserCardContainer: Avatarのフェッチ Scoreデータのフェッチ UserCard ScoreChart Blob Blob
  12. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Tip1: 画像の読み込み待機をSuspenseで管理する <template> <user-card :name="user.name" :description="user.description" :score="score" :blob-image-url="imageUrl" /> </template> <script setup lang="ts"> import { useBlobImageAsync, useScoreAsync, User, } from "../composables/useExternalData"; import UserCard from "../components/UserCard.vue"; const props = defineProps<{ user: User; }>(); const score = useScoreAsync(props.user.id); const imageUrl = await useBlobImageAsync(props.user.avatarUrl); </script> • setup関数で画像のURLを叩いて、blobにし、 さらに、URL.createObjectURLでURLで参照可 能な形式としておく。 • imgがSuspense中でマウントされる前段階 で、フェッチができる(プリフェッチ) • ↑の処理を非同期で呼び出すことで画像の読 み込みをSuspenseの管理に置くことができる • 注意点 読み込まれたデータはdocumentオブジェクト が持続する限り破棄されないので、場合によっ ては手動でblobを解放させる必要がある UserCardContainer
  13. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Tip1: 画像の読み込み待機をSuspenseで管理する export const useBlobImageAsync = async (url: string) => { const response = await fetch(url); const imageBlob = await response.blob(); return URL.createObjectURL(imageBlob); }; • setup関数で画像のURLを叩いて、blobにし、 さらに、URL.createObjectURLでURLで参照可 能な形式としておく。 • imgがSuspense中でマウントされる前段階 で、フェッチができる(プリフェッチ) • ↑の処理を非同期で呼び出すことで画像の読 み込みをSuspenseの管理に置くことができる • 注意点 読み込まれたデータはdocumentオブジェクト が持続する限り破棄されないので、場合によっ ては手動でblobを解放させる必要がある useExternalData
  14. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Tip1: 画像の読み込み待機をSuspenseで管理する <template> <div class="container"> <div class="profile"> <img class="icon" :src="blobImageUrl" /> <div> <h3 class="text-xl-bold">{{ name }}</h3> <p class="text-sm">{{ description }}</p> </div> </div> ….省略 </div> </template> <script setup lang="ts"> import ScoreChart from "./ScoreChart.vue"; import SkeletonScoreChart from "./SkeletonScoreChart.vue"; defineProps<{ name: string; description: string; blobImageUrl: string; score: Promise<number[]>; shouldWaitChartLoaded?: boolean; }>(); </script> • setup関数で画像のURLを叩いて、blobにし、 さらに、URL.createObjectURLでURLで参照可 能な形式としておく。 • imgがSuspense中でマウントされる前段階 で、フェッチができる(プリフェッチ) • ↑の処理を非同期で呼び出すことで画像の読 み込みをSuspenseの管理に置くことができる • 注意点 読み込まれたデータはdocumentオブジェクト が持続する限り破棄されないので、場合によっ ては手動でblobを解放させる必要がある UserCard
  15. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Tip2: データフェッチのwaterfallを防ぐ
  16. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Tip2: データフェッチのwaterfallを防ぐ 要件 • データのフェッチを待機している コンポーネントはスケルトンを表 示 • アイコン画像は全ての読み込み が終わったタイミングで一斉にス ケルトンを解除 • チャートはフェッチが完了したも のから順次表示
  17. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. App UserCardList: ユーザー一覧フェッチ Tip2: データフェッチのwaterfallを防ぐ UserCardContainer Avatarのフェッチ UserCard ScoreChart Scoreデータのフェッチ UserCardContainer Avatarのフェッチ UserCard ScoreChart Scoreデータのフェッチ
  18. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Tip2: データフェッチのwaterfallを防ぐ UserCardContainerのマウント
  19. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. App UserCardList: ユーザー一覧フェッチ Tip2: データフェッチのwaterfallを防ぐ UserCardContainer: Avatarのフェッチ Scoreデータのフェッチ UserCard ScoreChart UserCardContainer: Avatarのフェッチ Scoreデータのフェッチ UserCard ScoreChart Promise Promise
  20. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Tip2: データフェッチのwaterfallを防ぐ • 親コンポーネント(Container)でデータ フェッチを実行し、結果のPromiseを子コン ポーネント(Presentation)に渡す。子コン ポーネントでSuspenseによってPromiseを 解決する • データのフェッチをContainerで一括で実行 するためコンポーネントの凝集度も上がる <template> <user-card :name="user.name" :description="user.description" :score="score" :blob-image-url="imageUrl" /> </template> <script setup lang="ts"> import { useBlobImageAsync, useScoreAsync, User, } from "../composables/useExternalData"; import UserCard from "../components/UserCard.vue"; const props = defineProps<{ user: User; }>(); const score = useScoreAsync(props.user.id); const imageUrl = await useBlobImageAsync(props.user.avatarUrl); </script> UserCardContainer
  21. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. <template> <div class="container"> <div class="profile"> <img class="icon" :src="blobImageUrl" /> <div> <h3 class="text-xl-bold">{{ name }}</h3> <p class="text-sm">{{ description }}</p> </div> </div> <suspense :suspensible="shouldWaitChartLoaded"> <div class="chart"> <score-chart :score="score" /> </div> <template #fallback> <skeleton-score-chart /> </template> </suspense> </div> </template> <script setup lang="ts"> import ScoreChart from "./ScoreChart.vue"; import SkeletonScoreChart from "./SkeletonScoreChart.vue"; defineProps<{ name: string; description: string; blobImageUrl: string; score: Promise<number[]>; shouldWaitChartLoaded?: boolean; }>(); </script> Tip2: データフェッチのwaterfallを防ぐ UserCard • 親コンポーネント(Container)でデータ フェッチを実行し、結果のPromiseを子コン ポーネント(Presentation)に渡す。子コン ポーネントでSuspenseによってPromiseを 解決する • データのフェッチをContainerで一括で実行 するためコンポーネントの凝集度も上がる
  22. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Tip2: データフェッチのwaterfallを防ぐ UserCardContainerのマウント
  23. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. まとめ
 • Blobを用いることでimgタグの画像のフェッチもSuspenseで制御できる • 親コンポーネントで作成したPromiseをpropsで受け渡し、子コンポーネントで Promiseを解決することでデータフェッチのwaterfallを回避できる 本日用いたコード: https://github.com/reibomaru/vue-fes-2024
  24. 2024 GROWTH VERSE Co., Ltd. All rights reserved. This presentation

    is for informational purposes only. GROWTH VERSE Co., Ltd. makes no warranties, express or implied, in this summary. Thank you.