前回はコンポーネントを活用してページを作成していくことを試していきました。
今回はVue Routerを使って追加モードと編集モード
ゴール
- Vueのコンポーネントを整理しわかりやすいディレクトリにする
- Vue Routerによる画面遷移の実装ができるようになる
-
<router-link>
とJavaScriptによる遷移の2パターンを知る
Vue Routerのセットアップ
Vue Routerのインストールとpackage.json
Vue Routerをインストールして実際に使っていってみましょう。
こういったライブラリのインストールはnpm install
コマンドで実施します。
今回はvue-router
かつバージョンを4系を指定したいので以下のようになります。
$ npm install vue-router@4
インストールが完了したメッセージが流れたら、正しくインストールできたかを
package.json
を見て確認しましょう。
{
"name": "vite-vue-project",
"private": true,
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.2.25",
"vue-router": "^4.0.15"
},
"devDependencies": {
"@vitejs/plugin-vue": "^2.3.1",
"vite": "^2.9.7"
}
}
ここではじめてpackage.json
の説明をします。
このファイルは任意のnpmコマンドがscripts
、このプロジェクトが依存しているライブラリが
dependencies
とdevDependencies
に記載されています。
いつもnpm run dev
で開発環境を起動しているのはここに記載されているdev
を元に
npmがvite
コマンドを実行してくれてるからなのです。
今のようにnpm install
をするとインストールしたパッケージはdependencies
に残ります。
バージョンはコマンド実行してインストールされたものが記載されています。
devDependencies
は開発環境でのみ必要なパッケージで、
vite
などのビルドツールやテスティングツールなどが該当します。
このpackage.json
はGitリポジトリにコミットし管理されるものなので、
開発者は依存するライブラリとそのバージョンまでも共有することができるのです。
他の開発者は個別にパッケージ名を指定しなくても、npm install
コマンドだけで
このdependencies
とdevDependencies
を一挙インストールできます。
src配下のパスのalias対応
今回のチュートリアルの中でコンポーネントを色々整理して追加&移動していくにあたり、
./components/
や../
など相対パスで指定したものがそのままだと動かなくなります。
一つ一つ修正していくのも手なのですが、毎回パスの内容を確認したり、
修正のたびに修正が必要になりメンテナンス性が低いです。
これを解消するためにVite側でalias(エイリアス)の設定をすることができます。
aliasにより規定となるフォルダの位置をエイリアス~
により表現できます。
// これまで(`/src/components/sample/`からの場合)
import YourComponent from '../component/basics/YourComponent.vue'
// これから(aliasの既定パスを`src`に設定した場合)
import YourComponent from '~/components/basics/YourComponent.vue`
上のケースだと~
がsrc
フォルダ直下を指しているので、
このYourComponent
をインポートしているMyComponent
が例えbasics
の中にあろうと、
sample
の中にあろうと移動しようと問題がなくなるわけです。
逆にMyComponent
をインポートしているファイルが他にある場合は、
インポート先は修正が必要になりますが、それでも一括置換などで対応可能になります。
この設定はプロジェクトルートにあるvite.config.js
に追記することで実現できます。
ちなみにファイル名を指定して特定のファイルを開きたいときにはCtrl+Pが便利です。
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@': resolve(__dirname, './src'),
},
},
plugins: [vue()],
})
vite.config.js
はこれで良いので、続いて以下のようにファイル構造を修正します。
|-- assets
| |-- icon-plus.svg
| `-- logo.png
|-- components
| |-- basics(色々なところで使い回せる汎用性の高いコンポーネント)
| | `-- MyButton.vue
| |-- components(ページコンポーネントの中で使用するコンポーネント)
| | |-- TodoAdd.vue
| | `-- TodoList.vue
| `-- pages(Vue Routerで切り替えるページコンポーネント)
| |-- Edit.vue(新しい空のファイル)
| `-- Home.vue(新しい空のファイル)
|-- App.vue
|-- main.js
`-- router.js
そしてすでに実装済みのTodoAdd
でのインポート文をalias版で修正してみましょう。
// 修正前
import MyButton from './components/basics/MyButton.vue'
// 修正後
import MyButton from '@/components/basics/MyButton.vue'
あわせてボタンのプラス画像の場所もこのままだと異なっているので修正します。
<!-- 修正前 -->
<MyButton @click="todoAdd">追加<img src="../assets/icon-plus.svg" height="10" /></MyButton>
<!-- 修正後 -->
<MyButton @click="todoAdd">追加<img src="@/assets/icon-plus.svg" height="10" /></MyButton>
これだけだとイマイチ効果がわからないと思うので、続いてApp.vue
の内容をHome.vue
に移していきます。
App.vue
はヘッダーだけ残してあとはHome.vue
に切り出しインポートします。
<script>
import Home from '@/components/pages/Home.vue'
export default {
components: { Home },
}
</script>
<template>
<h1>My ToDo App</h1>
<Home />
</template>
<style>
body {
background-color: #eee;
}
</style>
Home.vue
は以下のように修正します。
<script>
import TodoAdd from '@/components/components/TodoAdd.vue'
import TodoList from '@/components/components/TodoList.vue'
export default {
components: {
TodoAdd,
TodoList,
},
data() {
return {
todos: [
//{ isDone: false, text: 'ToDoの文字列' }
],
}
},
methods: {
addTodo(newTodoText) {
if (!newTodoText) return alert('文字を入力してください')
this.todos.push({
isDone: false,
text: newTodoText,
})
},
clearDoneTodos() {
this.todos = this.todos.filter((todo) => !todo.isDone)
},
},
}
</script>
<template>
<TodoAdd @delete-done="clearDoneTodos" @add-todo="addTodo" />
<p v-if="todos.length === 0">ToDoがまだありません!</p>
<TodoList v-else :todos="todos" />
</template>
<style>
.todo-done {
text-decoration: line-through;
}
</style>
Todoに関する処理は完全に切り出していることがわかるかと思います。
続いて切り替え先のEdit.vue
もテンプレートだけ用意します。
<template>
<p>編集モード</p>
<input type="text" name="" id="" />
</template>
これでVueRouterの機能を使う準備が整いました。
router.js
作成
/src/router.js
を作成します。
Vue RouterのcreateRouter
を利用します。
import { createRouter, createWebHistory } from 'vue-router'
import Home from './components/pages/Home.vue'
import Edit from './components/pages/Edit.vue'
const routes = [
{ path: '/', name: 'Home', component: Home },
{ path: '/edit', name: 'Edit', component: Edit },
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
このコードで何をしているかというと、routes
でインポートしたコンポーネントに対して、
名前とパスの設定をしており、その設定を元にcreateRouter
でルーティング設定を作っています。
このエクスポートしているrouter
をmain.js
でインポートすることでルーティングが動作します。
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
これでVue Routerは効きますが、どこでページの切り替えを行うかをまだ明記していません。
今回のような作りの場合はApp.vue
に実装することが多いです。
App.vue
にルーティング切り替えのビューを定義する<router-view />
を定義します。
ついでにデフォルトでHome.vue
を表示する処理は消してしまいましょう。
<template>
<h1>My ToDo App</h1>
<router-view />
</template>
<style>
body {
background-color: #eee;
}
</style>
これでエラーも出ず表示が元と変わらなければ、うまくいっている証拠です。
ただ、画面がなにも変わらないので、リンクを押すと切り替わる<router-link>
を使ってみます。
router-link
による遷移
router-link
はVue Router利用時におけるa
リンクに変わるカスタムコンポーネントです。
router-link
を用いることで実際に画面をリロードすることなく内部の処理による画面遷移を実現します。
つまりa
によるリンクはVueによるシングルページアプリケーションでは使わないようにする必要があります。
<template>
<router-link to="/">Home</router-link> ・
<router-link to="/edit">Edit</router-link>
<h1>My ToDo App</h1>
<router-view />
</template>
<style>
body {
background-color: #eee;
}
</style>
これでヘッダーテキストの上に遷移リンクが表示されました。
to=""
の向き先はrouter.js
で定義したroutes
とマッチしています。
実際のアプリでもrouter-view
の前後にこのような形で画面遷移のメニューをヘッダーや、
ナビゲーションバーに用意することでどの画面でも共通で遷移メニューを表示することをすることがあります。
router.push
による遷移
プログラマブルに遷移の処理を任意のタイミングで実施することも出来ます。
追加したToDoに編集ボタンをつけてそこをクリックしたら編集モードに移動するように実装してみましょう。
Vueコンポーネント内部でthis.$router
によりVue Routerのインスタンスにアクセスできます。
そこからrouter.push
メソッドを呼び出して、パスを渡してみましょう。
<script>
import MyButton from '../basics/MyButton.vue'
export default {
components: {
MyButton,
},
props: {
todos: Array,
},
methods: {
openEditMode(todo) {
this.$router.push('/edit')
},
},
}
</script>
<template>
<ul>
<li v-for="todo in todos">
<input type="checkbox" v-model="todo.isDone" /><span
:class="{ 'todo-done': todo.isDone }"
>{{ todo.text }}</span
>
<MyButton @click="openEditMode(todo)">編集</MyButton>
</li>
</ul>
</template>
この状態でHomeでTodoを追加し、新たに表示された編集ボタンを押すとEdit画面に移れるはずです。
ですがこの状態だとEdit画面には遷移できますが、Homeに戻るとデータが消えてしまいます。
このように、VueのコンポーネントはVue Routerにより切り替えられるごとにインスタンスが生成されるため、
中のデータは揮発してしまうことに注意する必要があります。
この問題を回避するためには、サーバー側でデータを保持し、Vueのインスタンス生成時に
そのデータを取得して表示するか、あるいはVueのアプリ内ですぐに揮発しないストア領域を持つ必要があります。
次回のチュートリアルでは揮発しないストア領域を提供するライブラリPinia
を使って
ページ遷移してもデータが引き継がれる実装をしていきます。
また、Vue Routerの機能もより掘り下げて見ていきます。
まとめ
Vue Routerの基本を学び、router.js
の作り方や基本的な遷移方法を見てきました。
今回はいったんアプリを作ってから途中でVueRouterを導入しているのでファイルの移動やらが多く、
混乱しやすいかもしれませんが、一般的にはVueRouterが必要そうであれば最初からいれますし、
Viteのaliasの設定も最初から設定したほうが便利です。
もし新たなプロジェクトを始める機会があればぜひ最初から導入してみてください。