こんにちは、”はふぃ”です。

以前、画像をLazyLoad(遅延読み込み)することでパフォーマンス向上する方法を紹介しましたが、今度はAPIなどを通じてデータ取得する部分を遅延取得して、パフォーマンス向上させる方法を紹介します。

※ 画像のLazyLoadの記事はこちら ↓

データを遅延取得とは?

例としてAmazonのようなショッピングサイトを使って説明します。

サイトには販売する商品の画像が並んでいると思うのですが、それらのデータ(商品名や画像、値段など)を取得するためにAPIやDBを使ってデータを引っ張ってくる必要があります。

しかし、一つのページに並べる画像が多い場合、全てのデータを取得しきってからページを表示する必要があるのでパフォーマンスが悪くなりがちです。

ですが、ページを表示した時に最初に表示される領域(ファーストビュー)以外は少し遅れて表示されても問題ないはずです。

そこで、ファーストビュー以外の領域のデータを後から取得することで最初のページ表示時に必要なデータ量が減らし、パフォーマンスを向上させようというのが今回のテーマになります。

出典:Amazon

実装方法

サンプル

サンプルとして以下のようなページを利用します。

< components/pages/sample/LazyFetchPage/index.vue >

<template>
  <div class="lazy-fetch-page">
    <div class="row">
      <h1>家電</h1>
      <div class="column">
        <div class="card">
          <img src="~/assets/img/tv.png">
        </div>
        <div class="price">10,000円</div>
      </div>
      <div class="column">
        <div class="card">
          <img src="~/assets/img/reizouko.png">
        </div>
        <div class="price">20,000円</div>
      </div>
      <div class="column">
        <div class="card">
          <img src="~/assets/img/soujiki.png">
        </div>
        <div class="price">5,000円</div>
      </div>
    </div>

    <div class="row">
      <h1>日用品</h1>
      <div class="column">
        <div class="card">
          <img src="~/assets/img/water.png">
        </div>
        <div class="price">100円</div>
      </div>
      <div class="column">
        <div class="card">
          <img src="~/assets/img/tissue.png">
        </div>
        <div class="price">300円</div>
      </div>
      <div class="column">
        <div class="card">
          <img src="~/assets/img/shampoo.png">
        </div>
        <div class="price">1,000円</div>
      </div>
    </div>
  </div>
</template>

<script></script>

<style scoped>
.lazy-fetch-page {
  margin: 50px 10% 0 10%;
}

.row {
  margin-bottom: 80px;
}

.row h1 {
  margin-bottom: 30px;
}

.row:after {
  content: "";
  display: table;
  clear: both;
}

.column {
  float: left;
  width: 320px;
  height: 332px;
  margin-right: 40px;
}

.column .price {
  font-size: x-large;
  text-align: center;
  margin-top: 20px;
}

.card {
  box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); /* this adds the "card" effect */
  padding: 16px;
  text-align: center;
  background-color: #f1f1f1;
  height: 100%;
}

.card img {
  max-height: 100%;
  max-width: 100%;
}
</style>

本来はAPIやDBからデータを取得すると思うのですが、今回は簡略化のためにデータは最初からセットしておきます。その代わりにAPIからデータを取得していることを擬似的に表現するためにAPIからの取得時間分だけ setTimeout を使って待ちの時間を作ります。

< pages/sample/lazyFetch.vue >

<template>
  <lazy-fetch-page />
</template>

<script>
import LazyFetchPage from '~/components/pages/sample/LazyFetchPage/index.vue'

export default {
  components: {
    LazyFetchPage
  },
  async fetch (context) {
    // 「家電」のデータを取得する処理(APIから1秒間かけて取得する想定)
    const getAppliancesData = () => {
      return new Promise(resolve => setTimeout(resolve, 1000))
    }
    // 「日用品」のデータを取得する処理(APIから2秒間かけて取得する想定)
    const getDailyNecessitiesData = () => {
      return new Promise(resolve => setTimeout(resolve, 2000))
    }

    // 並列で取得
    await Promise.all([
      getAppliancesData(),
      getDailyNecessitiesData()
    ])
  }
}
</script>

この場合だと並列で取得しているものの、「日用品」のデータを取得するのに2秒かかるため、そっちに引っ張られてページが表示されるまで 2.4秒 かかります。

遅延取得

「家電」の領域をファーストビュー、「日用品」の領域をそれ以外と仮定して「日用品」のデータを取得する部分を遅延取得に変更していこうと思います。

Nuxt.js × Vue.js を使ったプログラムの場合、fetch はページがレンダリングされる前に行う処理ですが、そこからページが完全に表示されるまでに以下のようなタイミングがあります。

  • beforeCreate:インスタンスが初期化されるときに同期的に呼ばれる
  • created:インスタンスが作成された後に同期的に呼ばれる
  • beforeMount:インスタンスがマウントされる前に呼ばれる
  • mounted:インスタンスがマウントされた後に呼ばれる

この中の created のタイミングで「日用品」のデータを取得する処理を非同期で呼ぶことでページの表示を待たせることなくデータを取得する遅延表示を実現することができます。

< pages/sample/lazyFetch.vue >

<template>
  <lazy-fetch-page :isFetchedDailyNecessitiesData="isFetchedDailyNecessitiesData" />
</template>

<script>
import LazyFetchPage from '~/components/pages/sample/LazyFetchPage/index.vue'

export default {
  components: {
    LazyFetchPage
  },
  data () {
    return {
      isFetchedDailyNecessitiesData: false // 「日用品」のデータが取得完了したかどうか
    }
  },
  async fetch (context) {
    // 「家電」のデータを取得する処理(APIから1秒間かけて取得する想定)
    const getAppliancesData = () => {
      return new Promise(resolve => setTimeout(resolve, 1000))
    }

    // ファーストビューに必要なデータのみ取得
    await getAppliancesData()
  },
  created () {
    this.getDailyNecessitiesData()
  },
  methods: {
    // 「日用品」のデータを取得する処理(APIから2秒間かけて取得する想定)
    getDailyNecessitiesData () {
      return new Promise(resolve => setTimeout(() => {
        this.isFetchedDailyNecessitiesData = true
        resolve(true)
      }, 2000))
    }
  }
}
</script>

< components/pages/sample/LazyFetchPage/index.vue >

<template>
  <div class="lazy-fetch-page">
    <div class="row">
      <h1>家電</h1>
      …
    </div>

    <div v-if="isFetchedDailyNecessitiesData" class="row">
      <h1>日用品</h1>
      …
    </div>
    <div v-else class="loading">
      <img src="~/assets/img/loading.gif">
    </div>
  </div>
</template>

<script>
export default {
  props: {
    isFetchedDailyNecessitiesData: { type: Boolean, default: false }
  }
}
</script>

<style scoped>
…

.loading {
  text-align: center;
}
</style>

isFetchedDailyNecessitiesDataという「日用品」のデータを取得完了した時にtrueになる値を使って「日用品」の表示を制御(データを取得するまで表示しないように)

getAppliancesDataもmethodsに書けばいいのにと思うかもしれないが、fetchからmethodsの処理を呼び出すことはできないのでmethodsには書けない

結果

ファーストビューではない「日用品」の遅延表示を実現し、ページを表示するまでにかかる時間も早くなっています!(2.4秒 → 1.5秒

まとめ

遅延表示を使うことで、多くのデータを必要とするページでもパフォーマンスを落とすことなくページを表示することが可能となります。

ただし、表示する箇所によってはユーザビリティーを下げてしまうこともあるので、ローディングアニメーションを入れたり、ユーザーアクションの生じる箇所は避けるなど検討した上で導入しましょう。

参考

今回のコードはgithubで公開中なので参考にしてみてください!↓

https://github.com/hafilog/vue-sample/tree/master/components/pages/sample

投稿者: はふぃ

若手のWEBフロントエンジニア。最近はバレーボールにハマってます!

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

CAPTCHA