シングルページアプリケーションを制作する要件がありました。
色々検討した結果、今回はVite
とVue Router
でこれを実現することにしました。
要件
- シンプルなSPA
今回は最初から最後までプレゼンテーションのように全画面でページ遷移するものです。
ショッピングサイトのような、ページの一部だけ遷移させるSPAではありません。
使用するもの
- Vite
- Vue Router
制作手順
Vite導入
大体公式サイトの手順通りです。→はじめに|Vite
❯ nodenv glocal 18.16.0
❯ mkdir pjname
❯ npm create vite@latest
✔ Project name: … pjname
✔ Select a framework: › Vue
✔ Select a variant: › JavaScript
Node.jsはstableを入れました(本当は18.17.0だったけど、nodeenvにはなかったので18.16.0を)。
PJ作成時にフレームワークや言語を選びます。
今回はVue
とJavaScript
にしています。
(TSにしようか迷ったけど、vueも初めてなので使い慣れている方にしました…)
はい、もうこれだけでViteが動きます。
素晴らしいですよね。ありがたいですよね!!
❯ cd pjname
❯ npm run dev
Vue Router導入
Viteの基盤ができたら、そこにVue Router君を追加します。
これも大体公式の手順通りです。
❯ npm install vue-router
router.jsの作成
router.js
を作成して、そこにVue Routerの設定を書いていきます。
// Vue Routerパッケージのインポート
import { createRouter, createWebHistory } from 'vue-router'
const routes = []
// 一般的なルートの追加(動的インポート)
routes.push(
{
path: '/test',
name: 'test',
component: () => import('Test.vue'),
}
)
// 存在しないパスは全てTOPへ
routes.push(
{
path: '/:catchAll(.*)',
redirect: '/'
}
)
const router = createRouter({
history: createWebHistory(),
routes,
})
router.beforeEach((to, from) => {
// URL直接入力の場合はTOPへ
if (to.path != '/' && from.name == undefined) {
return '/'
}
})
export default router
ここでポイントがいくつかあります。
hashモード or Historyモード
Vue Routerのデフォルトはhashモードです。
色々違いはあるのですが、私がHistoryモードにしたのは、hashモードだとURLがhttp://localhost:8080/#/hoge
みたいに#が付いてしまうから。
違いがあるので要件に合う方を選べば良いかと思います。
historyモード選択時は注意点があるのですが、それは後述します。
静的インポート/動的インポート
component: () => import('../components/Test.vue')
のような書き方をすることで、各Vueファイルを動的インポートにしています。
これの何が良いかというと、ルート読み込み時に一気に全部のVueを読み込まず、このパスが呼び出された時にインポートしてくれるということです。
つまり、初回読み込みが速くなります。
今回は各ページがそれぞれ重く、またユーザーが全ページを読み込むとは限らないので基本的にこの動的インポートにしました。
じゃあ静的インポートはどないすりゃええかというと、こんな感じ。
import Test from 'Test.vue'
const routes = [
{
path: '/',
name: 'test',
component: Test,
}
]
存在しないURLの対応
存在しないURLを指定された場合は、大体Not Foundに飛ばすのかなーと思うのですが、今回は全てTOPにリダイレクトします。
routes.push(
{
path: '/:catchAll(.*)',
redirect: '/'
}
)
このコードですが、例えばルート/path
の場合は正しく制御できても、ルート/path/path
のような指定では上手くリダイレクトできませんでした(指定URLの真っ白ページが表示される)。調査したのですが未解決事項です。
URLの直接指定を拒否
認証後のページを認証前ユーザーに見せたくない、という要件が多そうですが、今回は全てのURL直接指定による遷移を防御したい要件でした。
TOPはOKとして、それ以外の存在するURLが直接指定された場合、パスをTOPに書き換えるという処理をしています。
これはナビゲーションガードという仕組みで実現しました。
router.beforeEach((to, from) => {
if (to.path != '/' && from.name == undefined) {
return '/'
}
})
「Vue Routerを介さないページ遷移ではfrom.name
に値が入ってこない」ということを利用してガードしています。
main.jsの設定
インストールしたVue Routerを、アプリ全体に読み込ませます。
import { createApp } from 'vue'
import router from './router'
import App from '../App.vue'
const app = createApp(App)
app.use(router)
app.mount('#app')
.htaccessの設定
Historyモード使用時の注意点です。
これだけでも開発環境では動くので油断するのですが、サーバに上げた途端、404エラーが出ます。
このままではルーティング制御ができないのです。
使用するWebサーバを確認して、それぞれに適した.htaccess
ファイルを置いてあげる必要があります。
history-mode.html#サーバーの設定例
私の場合はこのApacheの設定を入れてあげるだけでdev同様の動きになりました。
mod_rewrite
が使えない場合はもう少し困りそうですね…
全画面SPAの実現
htmlとapp.vueはこんな感じにしました。
<!-- 割愛 -->
<body>
<div id="app"></div>
<script type="module" src="js/main.js"></script>
</body>
<template>
<main>
<router-view v-slot="{ Component }">
<Transition name="switch-router">
<component :is="Component" />
</Transition>
</router-view>
</main>
</template>
これで<router-view>
のところに指定されたVueファイルが描画される感じです。
描画したVueファイルでは以下のように指定することで遷移を行っていきます。
<router-link to="パスの名前" replace>次へ</router-link>
jsで制御する場合はこんな感じ。
<script setup>
import { useRouter } from 'vue-router'
const router = useRouter()
router.replace('パスの名前')
</script>
Historyモードを使用しているので、router.push
にしてしまうと履歴が残ります。つまり、戻るボタンが使えてしまいます。
それも制御したかったのでrouter.replace
を使用しています。
router-link
を使用する場合は、replace
を記述してあげればOKです。
遷移アニメーションをつける
これで大体動作はOKなのですが、パキパキページが切り替わるのはカッコ悪いのでアニメーションをつけたいです。
遷移ごとにアニメーションを分けたい場合もあるでしょうが、今回は一律同じアニメーションで良かったので、呼び出し元のApp.vue
にトランジションアニメーションをつけてしまいました。
<router-view v-slot="{ Component }">
<Transition name="switch-router">
<component :is="Component" />
</Transition>
</router-view>
※なんでv-slot使っているのか思い出せません。何か理由はありました…何かが動かなくて変えたんです。。。
.switch-router-enter-active {
transition: opacity .7s;
opacity: 0;
}
.switch-router-enter-to {
opacity: 1;
}
これでなんとなくふんわりしたページ切り替えになりました。
あとがき
VueもViteも初めて触りました。
いろんなサイトの知見を参考にさせていただき、やっとやりたいことが実現できました。
ご指摘や修正は歓迎しています。