iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

VueRouterを使ったSPAに触れてみる

■はじめに

こんにちは。

株式会社iimonでフロントエンドを担当している「白水」です。

今回は「VueRouterを使ったSPAに触れてみる」というテーマで書いていきたいと思います。

■前提

SPAとは「Single Page Application」の略で、Webアプリケーションを構成する仕組みの1つです。

SPAの特徴として、同ページでコンテンツだけを切り替えることができます。

普段よく見るサービスは、SSR(サーバーサイドレンダリング)が多いと思いますが、SPAではCSR(クライアントサイドレンダリング)をします。

詳しくは、以下ページをご御覧ください。

tech.iimon.co.jp

scrapbox.io

■SPAのメリット・デメリット

◆メリット

  • ページ移動のたびにサーバーへリクエストして、サーバー側でHTMLを組み立てて・・・という処理が発生しないので、スムーズに動く。(表示速度のパフォーマンス向上)

 一般的なWebサイトはページを移動すると、ページ全体を再読み込みするため、画面が表示されるまで時間がかかります。  

 毎回、ページ全体を再読み込みして画面が切り替わるWebサイトなどを「マルチページアプリケーション = MPA」といいます。

 SPAでは変更があるパーツのみを読み込むためページを移動するたびの通信量が減り、ページの表示速度が向上します。

◆デメリット

  • HTML、CSSJavaScriptファイルなどを、初回にまとめて読み込むので、時間がかかる。

■環境構築

実際にSPAのページを体験してみたいと思います。

まずは、環境構築から始めます。

HTTPSか、SSHで、以下リポジトリをcloneしてください。

// HTTPS
❯ git clone https://github.com/shiramizu-junya/iimon-tech-blog.git

// SSH
❯ git clone git@github.com:shiramizu-junya/iimon-tech-blog.git

2024-05-20-vue-router-sampleディレクトリに移動します。

中身を見てみると、vue-cliで作ったファイルなどが入っています。

❯ cd 2024-05-20-vue-router-sample

vue-clipackage.jsonに含まれておらずエラーになったので、以下コマンドで@vue/cliをインストールしてください。

❯ npm install @vue/cli

npm installコマンドを実行して、ライブラリをインストールします。

❯ npm install

サーバーを起動しておきます。

❯ npx npm run serve

http://localhost:8080/にアクセスすると、ページが開かれます。

■VueCLIで作られた雛形のコードを確認してみる

  • ②:src/main.js

    createApp(App).mount("#app"); でApp.vueファイルをindex.htmlファイルのid属性がappになっている部分に表示しなさいという命令です。

    .use(router)vue-routerを使いますよという内容になっています。

  • ③:public/index.html

    これが雛形になるファイルで、このファイルの<div id="app"></div>の中に、Componentと言われるページを構成する部品が入ってきます。

  • ①:src/router/index.js

    ここにルーティングを定義して、そのルーティングに移動したら、component:で指定しているコンポーネントを表示しなさいという命令です。

  • ④:src/App.vue

    <router-link to="/">Home</router-link>がvue-rooterを使って、aタグを作るElementです。

    <router-view />の部分にコンポーネントが入れ込まれます。

  • ⑤:src/views/HomeView.vue

    <router-view />の部分に入れ込まれるコンポーネントです。

なので、"/"というパスに移動したら、component: HomeViewHomeView.vueファイルをコンポーネントとして使いなさいとなっているので、HomeView.vueファイルの<template>の中身を

<router-view />の中に入れて、最終的にpublic/index.htmlが表示されるという仕組みになっています。

■通信の中身を見てみる

初回、ページをリロードしたときは、複数のリクエストが発生して、雛形となるHTMLページだけを取得しています。

なので、public/index.htmlの中身がそのままレスポンスされているだけです。

Home → Aboutページに移動しても、ほとんど何も通信が走っていない事がわかると思います。

しかし、画面上の表示が切り替わっているのは、vue-routerでフロント側でコンポーネントが差し替えられるためです。

■動的ページを追加してみる

今回は、犬の画像が一覧で表示されるページを作成してみます。

◆ステップ1:viewファイルを作成

❯ touch src/views/ListView.vue

touchコマンドで、viewsディレクトリ配下にListView.vueファイルを作成します。

// src/views/ListView.vue

<template>
  <div>
    <h1>一覧ページ</h1>
    <button class="button" @click="getImage">画像を取得する</button>
    <button class="button" @click="moveHome">Home画面へ遷移</button>
    <div class="l-wrapper">
      <article class="card" v-for="imageUrl in imageUrls" :key="imageUrl">
        <div class="card__header">
          <figure class="card__thumbnail">
            <img
              v-bind:src="imageUrl"
              alt="Random Dog Image"
              class="card__image"
            />
          </figure>
        </div>
      </article>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      imageUrls: [],
    };
  },
  // ①
  mounted() {
    this.getImage();
  },
  methods: {
    getRandomNumber(min, max) {
      return Math.floor(Math.random() * (max - min + 1)) + min;
    },
    // ②
    async getImage() {
      // 1 ~ 20 までのランダムな値を取得
      const getImageCount = this.getRandomNumber(1, 20);
      const requestOptions = {
        method: "GET",
      };
      const response = await fetch(
        `https://dog.ceo/api/breed/hound/images/random/${getImageCount}`,
        requestOptions
      );
      const data = await response.json();
      this.imageUrls = data.message;
    },
    // ③
    moveHome() {
      this.$router.push("/");
    },
  },
};
</script>

<style scoped>
.l-wrapper {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 1rem;
  margin: 3rem auto;
  max-width: 1024px;
}

.card {
  flex: 0 1 calc(33.333% - 1rem);
  background-color: #fff;
  box-shadow: 0 0 8px rgba(0, 0, 0, 0.16);
  color: #212121;
  margin-bottom: 1rem;
}

.card__header {
  display: flex;
  flex-wrap: wrap;
}

.card__thumbnail {
  margin: 0;
  order: 0;
}

.card__image {
  width: 100%;
  display: block;
}

.button {
  background-color: #80cbc4;
  border: none;
  padding: 4px 8px;
  border-radius: 4px;
  margin: 4px;
  box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3);
}
</style>
  • ①:mounted()

    datamethodsの初期化は完了しているが、DOM(<template>の中)は生成されていない状態で実行されるフックです。

    DOMが生成される前に必要なデータをAPIから取得する処理をしています。

  • ②:getImage()

    APIにリクエストを投げて、データを取得する役割を担うメソッドです。

  • ③:moveHome()

    「Home画面へ遷移」ボタンをクリックすると、Home画面に遷移するためのメソッドです。

◆src/App.vueに一覧ページへ遷移するリンクを作成する

// src/App.vue

<template>
  <nav>
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link> |
    <router-link to="/list">List</router-link> // ここです
  </nav>
  <router-view />
</template>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

nav {
  padding: 30px;
}

nav a {
  font-weight: bold;
  color: #2c3e50;
}

nav a.router-link-exact-active {
  color: #42b983;
}
</style>

<router-link to="/list">List</router-link>を追加して、Listという名前のリンクを作成します。

◆VueRouterの設定を追加

// src/router/index.js

import { createRouter, createWebHashHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
import ListView from "../views/ListView.vue";

const routes = [
  {
    path: "/",
    name: "home",
    component: HomeView,
  },
  {
    path: "/about",
    name: "about",
    // route level code-splitting
    // this generates a separate chunk (about.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () =>
      import(/* webpackChunkName: "about" */ "../views/AboutView.vue"),
  },
  // ↓ここです
  {
    path: "/list",
    name: "list",
    component: ListView,
  },
];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

export default router;

path: "/list",で/listに遷移したら、ListView.vueファイルをコンポーネントとして使う設定をしています。

❯ npx npm run serve

これでサーバーを起動してみると、ページが表示されます。

◆画面の動きと通信を確認してみる

「List」というリンクをクリックすると、APIにリクエストを投げて、必要なデータだけを取得しています。

取得したデータを使って、フロントだけでページのコンテンツを表示しています。(HTMLは返ってこない)

「画像を取得する」ボタンをクリックすると、APIにリクエストを投げて必要なデータだけを取得して、ページのコンテンツはフロントで切り替えています。

「Home画面へ遷移」ボタンをクリックしても、サーバーへリクエストは投げないです、初回のリクエストで取得しているページ情報を使って、画面のElementを切り替えています。

■まとめ

いろんなサービスに触れていると、NetWork内でJSONデータしか返ってきてない or 静的なページでサーバーにリクエスト投げていないのでページのコンテンツが切り替わっているのは、今回のようにフロント側にルーティングを定義して、フロント側だけでページを更新する技術が使われているためです。 この記事を読んでくださった方の理解の助けになれれば幸いです。

また、弊社ではエンジニアを募集しております。

ぜひカジュアル面談でお話ししましょう!

ご興味ありましたら、ご応募ください!

Wantedly / Green