■はじめに
こんにちは。
株式会社iimonでフロントエンドを担当している「白水」です。
今回は「VueRouterを使ったSPAに触れてみる」というテーマで書いていきたいと思います。
■前提
SPAとは「Single Page Application」の略で、Webアプリケーションを構成する仕組みの1つです。
SPAの特徴として、同ページでコンテンツだけを切り替えることができます。
普段よく見るサービスは、SSR(サーバーサイドレンダリング)が多いと思いますが、SPAではCSR(クライアントサイドレンダリング)をします。
詳しくは、以下ページをご御覧ください。
■SPAのメリット・デメリット
◆メリット
- ページ移動のたびにサーバーへリクエストして、サーバー側でHTMLを組み立てて・・・という処理が発生しないので、スムーズに動く。(表示速度のパフォーマンス向上)
一般的なWebサイトはページを移動すると、ページ全体を再読み込みするため、画面が表示されるまで時間がかかります。
毎回、ページ全体を再読み込みして画面が切り替わるWebサイトなどを「マルチページアプリケーション = MPA」といいます。
SPAでは変更があるパーツのみを読み込むためページを移動するたびの通信量が減り、ページの表示速度が向上します。
◆デメリット
- HTML、CSS、JavaScriptファイルなどを、初回にまとめて読み込むので、時間がかかる。
■環境構築
実際に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-cli
がpackage.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: HomeView
でHomeView.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()
data
やmethods
の初期化は完了しているが、DOM(<template>
の中)は生成されていない状態で実行されるフックです。DOMが生成される前に必要なデータをAPIから取得する処理をしています。
②:
getImage()
③:
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 静的なページでサーバーにリクエスト投げていないのでページのコンテンツが切り替わっているのは、今回のようにフロント側にルーティングを定義して、フロント側だけでページを更新する技術が使われているためです。 この記事を読んでくださった方の理解の助けになれれば幸いです。
また、弊社ではエンジニアを募集しております。
ぜひカジュアル面談でお話ししましょう!
ご興味ありましたら、ご応募ください!