開発

はじめてのVue.js SPA(Single Page Application)をつくってみる

本投稿でも、前回の投稿に引き続き、Vue.jsを学ぶためにSPAについて見ていきたいと思います。

SoftwareDesign 2020年9月号 ステップアップ式 Vue.jsを参考に、理解を深めていきたいと思います。

SPAって?

SPAはSPA(SPA(Single Page Application)のこと。
ファーストランディングやブラウザの更新を除き、擬似的なページ遷移をJavaScriptとサーバーサイドのAPI通信だけで成立させることができるページのこと。

JavaScript上のオブジェクトから新しいDOMを生成するのは仮想DOMの得意技。
よって、より実現しやすかったのではないか?
Vue.jsはこの擬似的なページ遷移を実現するライブラリとしてVue Routerを提供している。
まるで、タグを使用するかのような感覚で擬似ページ繊維を実現することができる。

それに対し、通常のWebページはサーバーに複数のHTMLを置いて、ページ遷移ごとにそのHTMLを返してパスに応じてサーバー側のActionメソッドを呼び出したりする。
これを、分かりやすくマルチページアプリケーションと呼ぶ。

Ajaxを使用し、画面の一部分を書き換えることが可能。
この方法のメリットはページ遷移することなくサーバーの状態を受信し新しい画面を構築することができること

このときJavaScriptやCSSといったリソースを再度ダウンロードしたりパースしたりする必要がない。
受信したデータで新しいDOMを構成し直す為、ページ遷移より圧倒的にレスポンスがよくなる
同じように、ページ全体をレンダリングし直すと同じように高速の心地よい体験ができるのではないか?

どんなURLを受け取っても一つのエントリポイントに返すのはサーバーサイドでは普通にやっていたこと。
同じように、フロントエンドでパスを解析してレンダリングする内容を変化させることができる。
そして、それを促進させるものとしてブラウザにはHistory APIというAPIが実装されている。

DOM の Window オブジェクトは、ブラウザーのセッション履歴へのアクセスを history オブジェクトを介して提供しています。
このオブジェクトは、ユーザーの履歴の中を前のページや後のページへ移動したり、履歴スタックの中を称さしたりするのに便利なメソッドやプロパティが提供されています。

参照:History API

History インターフェイスで、ブラウザーのセッション履歴、つまり現在のページが読み込まれたタブまたはフレームで訪問したページを操作することができる。

これを使用することで現在のアドレスバーに入っているURLを置き換えて「戻る・進む」の履歴スタックにパスを積むことができる。

Vue Routerを導入する

Vue CLI では vue addコマンドを使用し、プラグインをインストールすることができる。

vue add

yarn add と比べると、vue add の良い点はモジュールの追加だけでなく既存のファイルの変更やファイル生成なども一緒に行ってる(一部においては)という点。

vue add によってインストールされたモジュールは

  • package.json
  • yarn.lock もしくは package-lock.json

によって管理される。

Vue CLI で作成したプロジェクトは作成時に git init されている。
よって、vue addする前に現在のコードをGitにコミットしよう。

次に、カレンドディレクトリをプロジェクトのワークスペースにして vue add router を実行する。
これにより、対話式のインストーラーが実行される。

 Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)

に、Yes(y)と答えると、App.Vueが大きく改変され、routerディレクトリとviewsディレクトリが作成される。
routerディレクトリのindex.jsにはどのパスでどのコンポーネントをルートコンポーネントとして読み込むのか定義する。
viewsディレクトリにはパスごとに読み込まれるルートコンポーネントが置いてある。

Noを選択するとどうなるの?

 Use history mode for router? (Requires proper server setup for index fallback in production) (Y/n)

で、No(n)と答えるとどうなるのか?

History Modeを使用するにはサーバー側でリダイレクトの設定が必要となる。
それができない場合、Noを選択し、そうするとハッシュモードとして動作する。

この形式で入力されるとvue-routerは、urlの # を見つけてそれより先の文字列を元に動的にコンポーネントを出し分ける。
また、サーバへの通信が発生しない分historyモードより若干早い。
しかし、URLに#が入ってしまう。

別のコンポーネントへの移動

今の状態で、

yarn serve

を実行する。

多くの場合は http://localhost:8080/ へアクセスする。
すると、画面の上部に 「Home | About」が表示されている筈。
そして、「About」をクリックすると、画面の表示内容がまるっと入れ替わり、なおかつブラウザのアドレスバーが
http://localhost:8080/about になっている筈。

これは通常のページ遷移と見分けがつかないのではないか?

どうやって実現しているのか?ルーティングの設定

src/router/index.js を見てみる、

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    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/About.vue')
  }
]

const router = new VueRouter({
  mode: 'history',
  base: process.env.BASE_URL,
  routes
})

export default router

変数、routesにルートオブジェクトの配列が入っているサンプルを見ることができる。
配列の為、既存のルート設定を参考に増やしていくと新しいルート定義を足すことができる。

HomeとAboutで、コンポーネントの読み込み方が違ってもいる。
Homeコンポーネントはアプリケーションを読み込んだ時点で一緒に読み込まれているのに対し、Aboutは関数の中で動的import構文が使用されている。
これにより、このパスにアクセスしたときだけAbout.vueが読み込まれるようになっている。

バンドラーを使ってアプリケーションを構築している時、バンドルされる JavaScript が非常に大きいものになり得ます。結果的にページのロード時間に影響を与えてしまいます。もし各ルートコンポーネントごとに別々のチャンクにして、訪れたルートの時だけロードできればより効率的でしょう。

Vue の 非同期コンポーネント機能
と webpack の コード分割機能 を組み合わせることでとても簡単に遅延ロードするルートコンポーネントができます。

Vue.js公式ルーター 遅延ローディングルートより

この、動的import構文はこのプロジェクトのひな型に組み込まれているwebpackのもの。
/* webpackChunkName: “about”*/ にも意味がある。

次に、App.vueを見てみる。

<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link>
    </div>
    <router-view/>
  </div>
</template>

<style lang="scss">
#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;

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

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

この中の、


タグについて注目してみる。

にはrouter/index.jsで定義するルートにより対応するコンポーネントが展開される場所になる。
つまり、パスが/のときはHomeコンポーネントがの位置に展開される

同じように、パスが/aboutのときはAboutコンポーネントがの位置に展開される。

その外側に

が置いてあるようにアプリケーション全体で共通のレイアウトはApp.vueで定義することができる。

コンポーネントの遷移

の部分はSPA内ではの代替として使用するリンクになる。

レンダリングされるときは、DOM上ではに置き換えられているが、内実はクリックイベントで起きる通常の遷移を抑制し、代わりにリンク先のコンポーネントを表示しブラウザの履歴に新しいURLをスタックする。

スタックはデータ構造の基本的なものの一つで、データを後入れ先出しの構造で保持するもの。

ここで、SPAのコンポーネントをで書いてしまうとSPA的な動きではなくページ全体(HTMLから)の再読み込みが始まってしまうので気をつける必要がある。

新しいルートを作成してみる・カウンターを表示するルートを作成しよう!

まず、

App.vue の、 vue add router を実行する前に復元する(もしその前にGitコミットしてたら)

App.vueを複製し、ファイル名をCount.vue にする(このCount.vueにを記載していく)

HelloWorldコンポーネントは削除
Vueオブジェクトのnameプロパティも不要なので削除

このままではCounter.vueのパス解決に失敗してしまう為、src配下からの相対パスとして@というエイリアスがあるのでこれを利用する。

src/views/Count.vue の中

<template>
  <div>
    <Counter name="Counter 1"
      :initCount="5" @emitUp="getEvent" />
    <Counter name="Counter 2"
      :initCount="10" @emitUp="getEvent" />
    {{ stack }}
  </div>
</template>


<script>
import Counter from '@/components/Counter.vue'

export default {
  components: {
    Counter
  },
  data(){
    return { 
      stack: []
    }
  },
  methods: {
    getEvent(payload){
      this.stack.push(payload)
    }
  }
}
</script>

src/views/Count.vue に、ルート定義してみる。
src/router/index.js のroutes変数の配列に下記内容を足す。

{
    path: '/count',
    component: () => import(/* webpackChunkName: "count" */ '@/views/Count.vue')
}

Countページへの遷移メニューを src/App.vue に下記内容を足す。

<router-link to="/count">Count</router-link>
  • 追加部分
<div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link to="/count">Count</router-link>
    </div>

おわりに

ここまで、簡単なSPAをつくってVue.jsについてみていってみました。
実際の記事ではVue.jsの歴史などにも触れてあって、概要をつかむのにおすすめです。
ぜひ、実際の記事も見てみてください。

SoftwareDesign 2020年9月号 ステップアップ式 Vue.jsを参考に、理解を深めていきたいと思います。

%d人のブロガーが「いいね」をつけました。