Vue CLI(Vue UI) + Vue Router + Vuex (+ vuex-persist) を使ってメモ帳アプリを作ろう

この記事は、「これからはじめる人のJavaScript/Vue.jsの教科書」のVue3対応、アップデート記事です。本書をお持ちの方は、後半の記事を以下の記事に差し替えてご利用ください。

Vue.jsは、JavaScriptと組み合わせて利用できる「フレームワーク」の1つで、簡単な手続きで本格的なウェブアプリを構築できるのが特徴です。

現在でも活発に開発されていて、2020年には最新のVue 3がリリースされました。

ここでは、そんなVue 3を利用し、複数ページを制御できるVue Router、データの管理を行うVuexと、ローカルストレージにデータを保存できるvuex-persistを利用した、メモ帳アプリの開発を紹介しましょう。

Vue CLI/Vue UIを利用しよう

Vueには、コマンドラインでVueのさまざまな操作を行える「Vue CLI(Command Line Interface)」と、そのVue CLIをブラウザー上で操作できる「Vue UI(User Interface)」というツールが提供されています。

まずはこれをインストールしておきましょう。なお、この操作には「Node.js」の「npm」というツールが必要です。もし、まだインストールしていない場合や、次のコマンドを入力した時に「Command Not Found(コマンドが見つかりません)」などのエラーメッセージが表示された場合は、次のサイトからプログラムをダウンロードして、先にNode.jsとnpmをインストールしておきましょう

node -v

Node.jsやnpmについて詳しくは、次の記事もご参照ください。

Vue CLIをインストールしよう

それでは、Vue CLIをインストールしましょう。macOSの場合はFinderから「アプリケーション→ユーティリティ→ターミナル」を起動します。Windowsの場合は、スタートボタンを右クリックして「Microsoft Terminal」を起動しましょう。そしたら、次のようにコマンドを入力します。

npm i -g @vue/cli

これでVue CLIがインストールされました。続けて、次のようにコマンドを入力してインストールされているかを確認しましょう。

vue -version

図のように表示されていれば、利用できます。

Vue UIを起動しよう

それでは、Vueのプロジェクトを管理する「Vue UI」を起動してみましょう。次のようにコマンドを入力します。

vue ui

ウェブブラウザーが起動して、図のような画面が表示されます。

もし画面が違う場合は、図のボタンでホームに戻っておきましょう。

新しいプロジェクトを作成しよう

ここでは、Vueのプロジェクトを作成できます。Vue自身は既存のHTMLファイルに後から組み込んで利用する事もできる、非常に手軽なフレームワークです。

しかし、その場合はこの後紹介する「コンポーネント」を作成するフォルダーの名前をなににするかなどのルールを自分で決めなければなりません。それでは、チームで開発する時などにルールを統一するのに手間がかかってしまうでしょう。

そこで、Vueでは標準としてフォルダー名の命名規則やファイル名の命名規則などを定め、それに沿った基本ファイル群を一気に生成してくれるビルドツールが提供されています。これをブラウザー上から利用できるのが「Vue UI」なのです。

では、ホームに戻ったら、図の「作成」ボタンをクリックしましょう。

プロジェクトを作成したいフォルダーを選んで、画面下の「ここに新しいプロジェクトを作成する」ボタンをクリックしましょう。

「プロジェクトフォルダ」にプロジェクトを作成するフォルダー名を入力します。

パッケージマネージャーはデフォルトの「npm」を利用していきます。これは、この後登場する「サーバー」を利用したりする際の管理ツールを指定するものです。ここでは、この記事の冒頭でインストールした「Node.js」の「npm」を引き続き利用します。他にyarnやpnpmなどが利用できますがここでは説明を省略します。

追加オプションはOFFのままで良いでしょう。「Gitリポジトリ」は、「バージョン管理システム」の利用の有無になります。ここでは説明を省略するので、知らないという方はOFFにしておくと良いでしょう。「次へ」をクリックします。

次に、Vueのバージョンなどを選ぶことができます。ここでは、「Default (Vue 3)」を選びましょう。「プロジェクトを作成する」ボタンをクリックします。

しばらく待つと、図のようなプロジェクトダッシュボードが起動します。これで準備完了です。

サーバーを起動しよう

Vue CLI(Vue UI)で作成したプロジェクトには、簡易的なサーバー機能が搭載されています。開発中は、常にこのサーバーを起動した状態で行って行きます。

それでは、図のボタンをクリックしてタスクの中から「serve」を選びましょう。

「タスクの実行」ボタンをクリックすると、サーバーが起動して図のような画面になります。もしここで、エラーなどが表示された場合は、他のサーバーが起動していないかなどを確認して、コンピューターを再起動するなどしてみてください。

正常に起動したら、図の「アプリを開く」ボタンをクリックしましょう。

図のような画面が表示されれば、サンプルで作成されたアプリが起動しています。

ファイルの構成を確認しよう

それでは、ここで作られたプロジェクトフォルダーの内容を確認していきましょう。Visual Studio Codeなどの開発エディターを利用すると便利でしょう。

node_modules

ここにVueの本体や、この後インストールするプラグインの本体ファイルなどが格納されます。このフォルダーを手で操作することはあまりありません。

public

ウェブサーバーが最初に確認するフォルダーです。この中には「index.html」が含まれていて、次のような内容が記述されています。

<noscript>
  <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->

JavaScriptが動作しない環境で起動した時に表示されるメッセージと、<div id="app"></div>というdiv要素だけがありますが、この「app」というid属性が後で重要な意味を持ちます。ここに、実際のプログラムの内容が表示されます。

もう1つ作られている「favicon.ico」は、ブラウザーのタブに表示されるアイコンです。実際に自分で利用するアイコンに差し替えるか、なければ削除してしまって良いでしょう。その場合は、「index.html」の次の記述も削除しておきましょう。

<link rel="icon" href="<%= BASE_URL %>favicon.ico">

src

このフォルダーが、普段開発する時に作業するフォルダーになります。標準ではApp.vuemain.jsというファイルが含まれています。これについては、この後紹介していきます。さらに、サブフォルダーがいくつかあります。

src/assets

プロジェクト内で利用する画像ファイルや、CSSファイルなどを配置するフォルダーです。標準では、logo.pngという画像ファイルが入っていますが、これは不要なので削除しておきましょう。

src/components

「コンポーネント」と呼ばれる、共通パーツを入れるフォルダーです。標準ではサンプルのHelloWorld.vueというファイルが配置されていますが、これは不要なので削除しておきます。

その他、次のファイルが配置されています。

・.gitignore: Gitで「無視」をするファイルのリスト
・babel.config.js: Babelというビルドツールで利用する設定ファイル
・jsconfig.json: ビルドツールのJavaScriptビルドの設定
・package.json: npmのパッケージを設定するファイル
・README.md: プロジェクトのドキュメントなどを記載するファイル
・vue.config.js: Vueの設定ファイル
・yarn.lock: Yarnというツールの設定ファイル

いずれも特に編集などは必要ないので、このままにしておきましょう。

この状態で、先ほど起動したウェブブラウザーに戻ってみましょう。画面が図のように英文のメッセージだらけになってしまいました。

これは、先ほどいくつかの不要ファイルを削除したため、ファイルのリンクが途切れてしまったエラーが表示されています。

src/App.vueファイルをエディターで開いて、このファイルの内容を空にしてしまいましょう。これでブラウザーに戻れば、エラーメッセージは消えて、真っ白の画面になりました。ここから開発をスタートすることができます。

vue-routerをインストールしよう

ウェブサイトを見ていると、見ているページによって、アドレスが次のように変化することがあります。

https://example.com/
↓
https://example.com/new

サーバーは、このようなアドレスを確認して、どのような情報をウェブブラウザーに渡すかというのを判断しています。これを「ルーティング」といい、Vueではこのようなルーティングを行うためのプラグインが提供されていて、簡単に実装できます。

ここでは「Vue Router」をインストールしてみましょう。Vueでは、プラグインのインストールなどもVue UI上で行えます。

Vue UIを起動して、「プロジェクトダッシュボード」にアクセスしましょう。

「プラグイン」ボタンをクリックすると、今インストールされているプラグインが一覧されます。

ここで新しいプラグインを追加します。Vue Routerは、よく利用されるため画面上部に直接インストールできるボタンが準備されています。これをクリックしましょう。

インストールが完了すると、いくつかのファイルが追加されたり、ファイルが勝手に変更されたりします。特にApp.vuemain.jsの内容は勝手に書き換わってしまうため、開発をはじめる前にインストールしておいた方が良いでしょう。

Vue Routerで追加されるファイル群

Vue Routerをインストールすると、いくつかファイルが追加されたり、変更されます。

/src/route

index.jsというファイルが配置されていて、ここでルーティングの設定が行われます。

/src/views

「ビューファイル」と呼ばれる

    <router-link to="/new">New</router-link> |

合わせて/src/App.vueファイルの画面上部にナビゲーション要素が追加されます。

メモの作成画面を作ろう

それではまずは、メモを作成する画面を作りましょう。src/App.vueを編集して、次の部分を確認しましょう。

  <nav>
    <router-link to="/">Home</router-link> |
    <router-link to="/about">About</router-link>
  </nav>

ここでは、画面上部のナビゲーション要素を構築しています。ここで利用しているrouter-linkという要素は、Vue Routerをインストールすると利用できる要素で、リンク(<a>)を作ることができます。

Vue Routerを利用している場合、HTMLの<a>要素をそのまま利用してしまうと、ルーティングが正しく行われなくなってしまうため、代わりに<router-link>を利用します。class属性などの各種属性はそのまま利用できますが、リンク先を示す属性はhrefではなく、toになります。

それではここに、新しいメモを作成する画面を示す/newを追加してみましょう。

  <nav>
    <router-link to="/">Home</router-link> |
    <router-link to="/new">New</router-link> |
    <router-link to="/about">About</router-link>
  </nav>

すると、ナビゲーションに「New」が追加されました。

ただし、現状ではクリックしてもなにも起こりません。Chromeの開発者ツールを確認すると、コンソールに次のように表示され、/newというパスがないことが分かります。

[Vue Router warn]: No match found for loccation with path “/new”

ルートを設定しよう

では、この/newというパスを準備しましょう。/src/router/index.jsファイルを開きます。

ここで、各パスの設定と、どんな内容が表示されるかを設定します。標準では、//aboutというパスが設定されていて、それぞれ「HomeView.vue」と「AboutView.vue」が利用される設定になっています。ここに、/newの設定を追加しましょう。

10行目付近に、次のように追加します。

{
  path: '/new',
  name: 'new',
  component: NewView
},

そして、「NewView」という定義をファイルの先頭に記述して、ここではこの後作成する「NewView.vue」というファイルと紐付けます。

import NewView from '../views/NewView.vue'

これでブラウザーに切り替えると、次のようなエラーメッセージが表示されます。

Module not found: Error: Can’t resolve ‘../views/NewView.vue’ in ‘/Users/seltzer/Sandbox/memopad-demo/src/router’

これは、まだ「NewView.vue」ファイルが準備されていないためです。続いてこれを作成しましょう。

ビューファイルを準備しよう

/src/views/フォルダーに、新しく「NewView.vue」というファイルを作成します。

次のように入力しましょう。

<template>
  <div><input type="text"></div>
  <div><textarea></textarea></div>
  <div class="center">
    <button>保存</button>
  </div>
</template>

これで画面を表示すると、図のようなフォームが表示されます。

ビューファイルでは、HTMLの基本タグ(DOCTYPEやbodyタグなど)は記述する必要がありません。これらは、/public/index.htmlに記述されている内容が利用されます。

代わりに、要素全体を<template>という特別なタグで囲む必要があります。内容は通常のHTMLがそのまま利用できます。

スタイルを調整しよう

今度はスタイルシートで少し調整していきましょう。スタイルシートは、外部のCSSファイルで制御することもできますが、ファイル内に記述してしまうと便利です。次のように<style>タグを追加しましょう。

<style scoped>
div {
  margin-bottom: 10px;
}
input[type=text] {
  width: 100%;
}
textarea {
  width: 100%;
  height: 30em;
}
button {
  width: 5em;
  margin: 3px;
}
.center {
  text-align: center;
}
</style>

<style>タグにはscopedという特殊な属性が付加されています。これは、「このファイル(コンポーネント)のみに適用するCSS」という事を示す属性で、これがないとすべての画面でスタイルが有効になってしまうため、特別な理由がなければ必ず付加しておきましょう。

全体のスタイルシートも調整しよう

全体のスタイルシートは/src/App.vue内で定義されています。すべて削除しても良いですが、ここでは一部を活かして、次のように調整しましょう。

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  max-width: 600px;
  margin: 0 auto;
}

h1 {
  text-align: center;
}

nav {
  padding: 30px;
  text-align: center;
}

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

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

図のような画面になれば、これで登録画面の完成です。

フォームをコンポーネントにしよう

ここで作成したフォームは、この後作成する編集画面でも利用します。そのため、両方の画面で利用できるように「コンポーネント」にまとめておくことができます。コンポーネントは/src/components/フォルダーにファイルを作成します。ここでは、MemoForm.vueを作成しましょう。

そして、/src/views/NewView.vueの内容をすべてコピーします。nameを登録しておきましょう。

<script>
export default {
  name: 'MemoForm'
}
</script>

空っぽになった、/src/views/NewView.vueでは、今作成したコンポーネントを利用するように次のように記述します。

<template>
  <MemoForm />
</template>

<script>
import MemoForm from '@/components/MemoForm.vue'
export default {
    name: 'NewView',
    components: {
        MemoForm
    }
}
</script>

作成したファイルをインポートして、これをcomponentsに宣言します。すると、<MemoForm />として利用できるようになります。

これで再度動作を確認して、正しく動作していることを確認しましょう。これでコンポーネントに分けることができました。他の画面でも利用できるようになります。

Vuexで状態管理をしよう

ここで、作成したメモのデータはどこに保存したら良いでしょう? Vueだけを利用する場合には変数などに記憶しておくだけでよいですが、Vue Routerを利用した複数のページ構成があるプロジェクトの場合、変数に保存するだけでは画面を移動した時に消えてしまいます。

そこで、「状態管理」というしくみを使います。これには、状態管理ライブラリーを利用すると良いでしょう。Vueでは「Vuex」というしくみがよく使われています。

それでは、このプラグインをインストールしましょう。Vue UIで「プラグイン」を選びます。Vuexも非常によく使われるライブラリーなため、画面上部にボタンがあるのでこれをクリックしましょう。

インストールが終わると、次のファイルが追加・変更されています。

/src/store/index.js

Vuexの「ストアー」というしくみを利用するためのファイルです。あらかじめ必要な記述が含まれているので、ここにプログラム内容などを書き加えていくことになります。

main.js

Vuexを利用するための宣言が追加されます。

それでは、Vuexを利用してみましょう。

Vuexにデータを保存しよう

それでは、Vuexにデータを保存してみましょう。まずは、フォームとの連携はせずに「ボタンを押したらてきとうなデータが保存される」というところまで作成してみましょう。

/src/store/index.jsをエディターで開きます。ここには、次のような記述が続いています。

  • state: アプリケーション全体でアクセスできるデータを宣言します
  • getters: stateの内容を取得するためのメソッドを定義します
  • mutations: stateの内容に変化を与えるためのメソッドを定義します
  • actions: 非同期処理をするための処理を定義します
  • modules: 処理をモジュールに分割した時の処理を定義します

この記事では、「actions」「modules」については触れませんので、公式リファレンスなどをご参照ください。

まずは、「state」に保存する内容の入れ物を定義します。ここでは、メモを保管するための「memos」という配列を準備しましょう。次のように書き加えます。

state: {
  memos: []
},

そしたら、このステートにメモの内容を保存するための処理を「mutations」に定義します。次のように追加しましょう。

mutations: {
    /* メモを保存する */
    save (state, newMemo) {
      state.memos.unshift(newMemo)
    }
}

これで準備完了です。定義した「memos」というステートにアクセスするにはstate.memosと記述します。これは先に配列として定義したためunshiftメソッドを使って、先頭に要素を追加しました。

mutationsの定義では、1つめのパラメーターとして「state」を指定する決まりがあります。実際のパラメーターは2つめ以降に指定しましょう。

これで準備完了です。

イベントを定義しよう

では次に、/src/components/MemoForm.vueを編集して、ボタンをクリックしたらデータが保存されるようにしましょう。

まずは、JavaScriptの決まり文句を記述します。

<script>
export default {
}
</script>

ここに処理を追加します。なお、この記事ではVueの基本的な内容は省略するため、あいまいな場合はVue自体についての学習をしてみましょう。

ここでは、ボタンをクリックした時のメソッドを定義します。

methods: {
  save() {
    let memo = {
      title: 'メモのタイトルです',
      content: 'メモの内容です'
    }

    this.$store.commit('save', memo)
  }
}

mutationsに定義したミューテーションには、this.$store.commitというメソッドを使って呼び出します。1つめのパラメーターにミューテーションの名前、2つめ以降のパラメーターに必要なパラメーターを指定します。ここでは、仮の内容を作って指定しました。

そしたらこのメソッドを、「保存」ボタンをクリックするタイミングで発動させます。ボタンを次のように変えましょう。

<button @click="save">保存</button>

これで完成しました。

Vue.js devtoolsで動作を確認しよう

これで、新しいメモを作成して「保存」ボタンをクリックすると、ストアにデータが保存されます。ただし現状では、その様子を確認することができません。

そこで、Google Chromeに「Vue.js devtools」という拡張機能をインストールしましょう。

インストールすると、ツールバーに図のアイコンが追加されますが、実際には「開発者ツール」を起動して利用します。Vueで作成されたページを表示すると、開発者ツールに「Vue」というタブが追加されます。

ここで、動作しているプログラムのさまざまな情報が確認できます。Vuexタブをクリックしましょう。図のようにステートの内容が表示され、データが追加されている様子が確認できます。

フォームと接続しよう

では、実際にフォームと接続しましょう。まずは、dataを定義します。

// /src/components/MemoForm.vue

  data() {
    return {
      title: '',
      content: ''
    }
  },

そして、これをv-modelディレクティブでフォームの項目と接続します。

<!-- /src/components/MemoForm.vue -->
  
<div><input type="text" v-model="title"></div>
<div><textarea v-model="content"></textarea></div>

これで、フォームに入力した内容を扱えるようになります。saveメソッドの内容を変更して、フォームの内容を保存するように変更しましょう。

// /src/components/MemoForm.vue

save() {
  let memo = {
    title: this.title,
    content: this.content
  }

  this.$store.commit('save', memo)
}

これで、実際にフォームに入力した内容がストアに保存されるようになりました。

IDを挿入しよう

複数のデータがある場合は、それを特定できる「キー」を設定しておくと、後で扱いやすくなります。ここでは、ストアのミューテーションでIDを割り振れるようにしましょう。まずは、件数を記録するcountというステートを定義しておきます。

// /src/store/index.js

state: {
  count: 0,
  memos: []
},

そしたら、保存するメモの内容にIDを指定してから保存します。これは、countを1ずつ加えながら、新しいIDを割り振っていきましょう。

// /src/store/index.js

save (state, newMemo) {
  newMemo.id = ++this.state.count
  state.memos.unshift(newMemo)
}

こうして、メモを保存すると新しいIDが割り振られてストアに保存されていきます。

トップページに戻そう

現状では、新しいメモを保存しても画面がそのままのため、同じメモが何度も保存されてしまいます。そこで、保存できたらホームの画面に移動しましょう。これには、$router.pushというメソッドを使います。saveメソッドに以下のように追加しましょう。

// /src/components/MemoForm.vue

save() {
...
  this.$router.push('/')
}

一覧画面を作成しよう

続いて、保存したメモを一覧できる画面を作成しましょう。これは、このプロジェクトのトップページに実装したいため、すでに準備されているHomeView.vueを変更していきましょう。

次のようにファイルに書き込みます。

<template>
  <div class="home">
    <ul>
      <li></li>
    </ul>
  </div>
</template>

ここに、ストアに保存されているメモの一覧を表示しましょう。JavaScriptを記述していきます。

<script>
export default {
  name: 'HomeView',
  computed: {
    memos () {
      return this.$store.state.memos
    }
  }
}
</script>

ここでは、「computed」にストアのメモの配列を取得するための「memos」を定義しました。これを使って、v-forディレクティブを作成していきます。

<li>タグを次のように変更しましょう。

<li v-for="memo in memos" :key="memo.id">
  {{ memo.title }}
</li>

これでブラウザーを確認してみましょう。現状ではストアの内容が空っぽに戻ってしまうため、画面が真っ白になってしまいます。「New」をクリックして、何度か保存ボタンをクリックしてから、「Home」に戻ってみましょう。一覧が表示されます。

メモがないときの画面を作成しよう

メモが一件もない場合、現状では画面が真っ白になってしまいます。そこで、v-ifディレクティブを使ってメモがある時の表示とない時の表示を切り替えましょう。次のように変更します。

<div class="home">
<ul v-if="hasMemos">
  <li v-for="memo in memos" :key="memo.id">
    {{ memo.title }}
  </li>
</ul>
<p v-else>メモはありません</p>
</div>

「hasMemos」は、「computed」で次のように宣言します。

hasMemos() {
  return this.$store.state.memos.length
}

ゲッターを宣言しよう

ストアーには「ゲッター」を宣言することができます。これは、ステートの値を取得するための専用のメソッドです。先のプログラムで、メモの件数を取得するのに、直接配列のlengthプロパティを使って件数を調べていましたが、このような場合はゲッターを宣言しておいた方が、後でプログラムを拡張したりするときに拡張しやすくなります。

そこで、ここではgetCountというゲッターを宣言してみましょう。

/src/store/index.jsを編集しましょう。gettersの部分に次のように定義します。

getters: {
    getCount: (state) => {
      return state.memos.length
    }
}

そしたら、/src/views/HomeView.vueの中では次のように、このゲッターを呼び出して利用します。

    hasMemos() {
      return this.$store.getters.getCount
    }

これで、メモの件数を取得でき、それによってリストの表示を変えられるようになりました。

スタイルを調整しよう

最後にHomeView.vueファイルのスタイルを調整しておきましょう。次のように追加します。

<style scoped>
ul {
  margin: 0;
  padding: 0;
}
li {
  list-style: none;
  border-bottom: 1px solid #ccc;
  padding-bottom: 10px;
  margin-bottom: 10px;
}

li a {
  color: #999;
  text-decoration: none;
  width: 100%;
  display: block;
}
</style>

これで、一覧画面の完成です。

詳細・編集画面を作成しよう

続いて、一覧画面からメモをクリックしたら、内容を確認したり修正・削除できる画面を作りましょう。ここでは、新しいルート定義として/editを定義します。

/src/router/index.jsファイルを編集します。15行目付近に次のように追加しましょう。

{
  path: '/edit',
  name: 'edit',
  component: EditView
},

ファイルの先頭にコンポーネントの宣言も追加します。

import EditView from '../views/EditView.vue'

そしたら、ここで宣言したEditView.vueファイルを作成しましょう。次のように内容を追加します。

<template>
Edit View
</template>

次のようにアドレスを直接指定すると、「Edit」などと表示されていれば完了です。

パラメーターを受け取ろう

ここで、編集画面では「どのメモを表示するか」を指定する必要があります。このような場合、通常のウェブのシステムではアドレスにIDなどを指定する設計にする事が多いでしょう。

例)

http://localhost:8080/#/edit/1
http://localhost:8080/#/edit/2

このようなアドレスを指定できるようにするには、ルートの定義でパラメーターを受け取れるようにします。/src/router/index.jsを編集しましょう。

  path: '/edit/:id',

これで、上記のようなアドレスが指定できます。今度はここで指定された内容をプログラムで利用できるようにしましょう。

アドレスで指定されたパラメーターを利用するには$route.params.(パラメーター名)として利用できます。例えばここでは、idというパラメーター名で定義しているので、次のようにすると画面にパラメーターを表示することができます。

Edit view: {{ $route.params.id }}

メモを呼び出そう

こうして、アドレスで特定のメモを指定できるようになったので、これを呼び出して取り出せるようにしましょう。

まずは、ストアでゲッターを定義します。

// /src/store/index.js

getMemoById: (state) => (id) => {
  return state.memos.find(memo => memo.id === id)
},

配列のfindメソッドを使って、指定されたidと一致するメモを取得することができます。これを使って、編集画面でメモを取り出してみましょう。

<script>
// /src/views/EditView.vue
export default {
  name: 'EditView',
  computed: {
    memo() {
      let id = parseInt(this.$route.params.id)
      return this.$store.getters.getMemoById(id)
    }
  }
}
</script>

これでmemoで内容を取得できるようになったので、<template>内を次のように変更しましょう。

<template>
{{ memo.title }}
</template>

一覧画面と連携しよう

ここまでできたら、一覧画面でメモをクリックしたら画面が切り替わるようしましょう。次のように<router-link>でリンクを張ります。

<!-- /src/views/HomeView.vue -->
<router-link :to="{ name: 'edit', params: { id: memo.id } }">{{ memo.title }}</router-link>

<router-link>でパラメーターを渡す場合は、定義したルートの名前などを利用して指定する必要があります。これで、次のようなアドレスが作られるようになります。

コンポーネントに表示しよう

続いて、メモ作成画面で作ったコンポーネントを使って、編集画面に仕上げていきましょう。

まずは、コンポーネントにメモの情報を渡せるように変更していきます。コンポーネントが値を受け取れるようにするにはpropsというしくみを利用します。次のように宣言しましょう。

// /src/components/MemoForm.vue
  props: [
    'memo'
  ],

これにより、コンポーネントを呼び出すときに次のように属性を指定できるようになります。メモ作成画面を次のように変更しておきましょう。

<!-- /src/views/NewView.vue -->
<MemoForm :memo="" />

これで準備完了です。

では、同じようにしてEditView.vueを変更していきましょう。

<template>
  <!-- /src/views/EditView.vue -->
  <MemoForm :memo="memo" />
</template>

<script>
import MemoForm from '@/components/MemoForm.vue'
export default {
  name: 'EditView',
  components: {
    MemoForm
  },
  computed: {
    memo() {
      let id = parseInt(this.$route.params.id)
      return this.$store.getters.getMemoById(id)
    }
  }
}
</script>

これでコンポーネントに、メモの内容が渡されるようになったので、これを使ってフォームに初期値を設定していきます。再びMemoForm.vueを編集して、dataの定義を次のように変更しましょう。

// /src/components/MemoForm.vue
  data() {
    return {
      title: this.memo.title,
      content: this.memo.content
    }
  },

これで、メモの内容がフォームに反映されるようになりました。

パラメーターが正しくない場合の処理をしよう

アドレスはユーザーが自由に書き換えられてしまうため、存在しないIDを指定してしまったり、いたずらでおかしな値を指定されることが考えられます。そこで、v-ifディレクティブを使ってコンポーネントの表示を制御しましょう。

<!-- /src/views/EditView.vue -->
<MemoForm :memo="memo" v-if="memo" />
<p v-else>指定されたメモはありません</p>

メモを編集できるようにしよう

現状では、編集フォームでメモの内容を変更すると、新しいメモとして登録されてしまいます。そこで、saveメソッドを改良して、既存のメモの場合は内容を変更するようにしましょう。

// /src/components/MemoForm.vue
save() {
  let memo = {
    title: this.title,
    content: this.content
  }

  if (this.memo.id) {
    memo.id = this.memo.id
  }

  this.$store.commit('save', memo)
  this.$router.push('/')
},

既存のメモの場合、「memo.id」に値が挿入されているため、これを合わせてストアーのsaveに送るようにします。

そしたら、ミューテーションを次のように変更しましょう

// /src/store/index.js
save (state, newMemo) {
  if (newMemo.id) {
    let x = state.memos.find(memo => memo.id === newMemo.id)
    x.title = newMemo.title
    x.content = newMemo.content
  } else {
    newMemo.id = ++this.state.count
    state.memos.unshift(newMemo)
  }

こちらも、idが渡された場合は、メモの変更になるので、既存の配列の内容を変更するように変更しました。これによって、メモの変更が行えるようになりました。

メモを削除しよう

次は、メモの削除のしくみを作成しましょう。まずは、ストアで特定のデータを削除するミューテーションを定義します。

// /src/store/index.js
delete (state, id) {
  state.memos = state.memos.filter(memo => memo.id !== id)
}

配列のfilterメソッドを使って、指定されたID以外のデータだけを抽出し、それを改めてステートに保存し直します。これによって、IDで指定したメモのデータだけが除外されるというしくみです。

では、ここで作ったミューテーションを呼び出していきましょう。編集画面に「削除」ボタンを追加します。

<!-- /src/components/MemoForm.vue -->
<div class="center">
  <button @click="save">保存</button>
  <button @click="remove" v-if="memo.id">削除</button>
</div>

v-ifディレクティブで、memo.idがある場合のみ、つまり編集画面の場合だけ、ボタンを表示するようにしました。クリックするとremoveが呼び出されます。これを定義しましょう。

// /src/components/MemoForm.vue
methods: {
  ...
  remove() {
    this.$store.commit('delete', this.memo.id)
    this.$router.push('/')
  }
}

これで、メモが削除されるようになりました。

vue-persistでデータを保存しよう

これで、メモ機能の一連の流れができあがりました。ただし、このメモツールは、ブラウザーを閉じたり、再読込するとデータが消えてしまいます。これは、JavaScriptの変数上にしかデータが存在していないためです。

そこで、このデータをブラウザーの「ローカルストレージ」と呼ばれる記憶領域に保存して、同じウェブブラウザー上であれば利用し続けられるようにしましょう。

これには、「vuex-persist」というライブラリを利用するのが簡単です。

Vue UIを起動したら、「依存」をクリックして「依存をインストール」をクリックします。

検索窓に「vuex-persist」と入力して、ライブラリを探しましょう。これをインストールします。

vuex-persistを利用する場合は、ほとんど決まり文句を記述するだけです。ストアに、次のように追加しましょう。

// /src/store/index.js
import { VuexPersistence } from 'vuex-persist'

...

const vuexPersist = new VuexPersistence({
  storage: localStorage
})

mutations: {
  RESTORE_MUTATION: vuexPersist.RESTORE_MUTATION,

...

  plugins: [vuexPersist.plugin]

vuex-persistを利用して、保存先を「localStorage」に設定します。後は、ミューテーションの時にvuex-persistを利用するように設定しています。

これで、メモの内容がローカルストレージに移されます。Chromeの開発者ツールで「アプリケーション→ローカルストレージ」を確認すると、ストアの内容が記憶されていることが分かります。これでメモを作成すると、ブラウザーを終了して、再度起動しても内容が呼び戻されるようになります。

これで、Vueを利用したメモツールが完成しました。さまざまな知識が出てきてややこしく感じますが、Vueの基本をしっかり押さえ、VuexとVuex Routerの使い方を学べば、さまざまなツールが開発できるようになるため、ぜひ利用してみましょう。

この記事を書いた人

たにぐち まこと

『よくわかるPHPの教科書』や『マンガでマスター プログラミング教室』の著者。 ともすた合同会社で、プログラミング教育やこども向けの講座などを Udemyや YouTubeで展開しています。