Vue js Vuexの使い方とvuexを使用した連絡先追加フォームの作成

Vue cli をインストールする際にデフォルトではなくカスタムを選択し、Vuexを選択すると使える。

storeフォルダのindex.jsのファイルを記述していくことで使う

ステイト、ミューテーション、アクション、ゲッターの機能がある

 

デフォルトのコードは以下

 

src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
drawer:false
},
mutations: {
},
actions: {
},
modules: {
}
})

importでvuexを読み取り、useで使用できるようになっている。

 

別の〇〇.vueファイルを開いた際、storeへアクセスするにはテンプレートから「$store」でアクセス可能。

stateは$store.stateで参照できる

 

 

サイドメニューを開閉するページを作る例を使って覚えよう

src/store/index.js / SideNav.vue / App.vueというファイルを編集する中での具体例を以下に記述する

 

デフォルトはこちら

 

 

index.js

 
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
})

 

SideNav.vue

<template>
<v-container fluid fill-height>
<v-btn color="pink" dark @click.stop="drawer = !drawer">Toggle</v-btn>

<v-navigation-drawer v-model="drawer" absolute temporary>
<v-list>
<v-list-item>
<v-list-item-avatar>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>Kazuya Kojima</v-list-item-title>
</v-list-item-content>
</v-list-item>

<v-divider></v-divider>

<v-list-item v-for="(item, index) in items" :key="index">
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</v-container>
</template>

<script>
export default {
data () {
return {
drawer: false,
items: [
{ title: '連絡先一覧', icon: 'mdi-menu' }
]
}
}
}
</script>
 

 

App.vue

<template>
<v-app>
<div>
<v-app-bar
color="grey darken-2"
dark
>
<v-app-bar-nav-icon></v-app-bar-nav-icon>
<v-toolbar-title>マイアドレス帳</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
</div>
<SideNav/>

</v-app>
</template>

<script>
import SideNav from './components/SideNav'

export default {
name: 'App',

components: {
SideNav,
 
},

data: () => ({
//
}),
};
</script>

 

 

この記述ではまだstoreとの連携はない

f:id:kirikko_Scondcube:20200802203654p:plain

f:id:kirikko_Scondcube:20200802203659p:plain

toggleボタンを押すと、サイドメニューが表示される

 

 

これを以下に変更すると

メニューボタンを押してサイドメニューの開閉が可能に

 

index.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
drawer:false

},
mutations: {
toggleSideMenu(state){
state.drawer = !state.drawer
}
},
actions: {
toggleSideMenu({commit}){
commit('toggleSideMenu')
}
},
modules: {
}
})

 

SideNav.vue

<template>
<v-container fluid fill-height>
 
<v-navigation-drawer v-model="$store.state.drawer" absolute temporary>
<v-list>
<v-list-item>
<v-list-item-avatar>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>Kazuya Kojima</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
<v-list-item v-for="(item, index) in items" :key="index">
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</v-container>
</template>
<script>
export default {
data () {
return {
items: [
{ title: '連絡先一覧', icon: 'mdi-menu' }
]
}
}
}
</script>

変更点

v-modelを「$store.state.drawer」に変更

 

App.vue

<template>
<v-app>
<v-app-bar
app
color="primary"
dark
>
<v-app-bar-nav-icon @click.stop="openSideMenu"></v-app-bar-nav-icon>
<v-toolbar-title>マイアドレス帳</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-content>
<SideNav/>
</v-content>
</v-app>
</template>
<script>
import SideNav from './components/SideNav'
export default {
name: 'App',
components: {
SideNav
},
data: () => ({
//
}),
methods: {
openSideMenu () {
this.$store.dispatch('toggleSideMenu')
}
}
};
</script>

変更点

<v-app-bar-nav-icon @click.stop="openSideMenu"></v-app-bar-nav-icon>
 とし、サイドメニューのアイコンをクリックしたときにopenSideMenuのメソッドを実行するようにした。
methods: {
openSideMenu () {
this.$store.dispatch('toggleSideMenu')
}
}
そしてmethodsの記入をした。
コンポーネントインスタンス上では「this.$store」でstoreにアクセスができる。
dispatchiはstoreのメソッド
dispatchi()でアクションを呼び出せる

 

src/store/index.js

actions: {
toggleSideMenu({commit}){
commit('toggleSideMenu')
}
},

で定義している

 

mapActionsを使用した書き換え

 

vuexにはmapActionsと言うメソッドがあり、これを使用することで上記のアクションを使用したサイドメニューの開閉をよりシンプルに定義できる。

 

書き換えは以下(結果は同じ)

 

App.vue

<template>
<v-app>
<v-app-bar
app
color="primary"
dark
>
<v-app-bar-nav-icon @click="toggleSideMenu"></v-app-bar-nav-icon>
<v-toolbar-title>マイアドレス帳</v-toolbar-title>
<v-spacer></v-spacer>
</v-app-bar>
<v-content>
<SideNav/>
</v-content>
</v-app>
</template>
<script>
import {mapActions} from 'vuex'
import SideNav from './components/SideNav'
export default {
name: 'App',
components: {
SideNav
},
data: () => ({
//
}),
methods: {
...mapActions(['toggleSideMenu'])
}
};
</script>

script以下に

import {mapActions} from 'vuex'

を追加。

先ほどあったmethodsの

openSideMenu () {
this.$store.dispatch('toggleSideMenu')
}
を削除し
...mapActions(['toggleSideMenu']) に変更する。 
そして呼び出しの@clickを<v-app-bar-nav-icon @click="toggleSideMenu"></v-app-bar-nav-icon> へ変更する
 

サイドバーでリンクの遷移を作る

 
SideNav.vueリンクを先の追加→ホーム画面から別ページ(src/Addresses.vue)へのリンクの設定をする
 
変更前
 
SideNav.vue
<template>
<v-navigation-drawer v-model="$store.state.drawer" absolute temporary>
<v-list>
<v-list-item>
<v-list-item-avatar>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>Kazuya Kojima</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
<v-list-item v-for="(item, index) in items" :key="index">
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
data () {
return {
items: [
{ title: '連絡先一覧', icon: 'mdi-menu' }
]
}
}
}
</script>
 
 
これを
 
<template>
<v-navigation-drawer v-model="$store.state.drawer" absolute temporary>
<v-list>
<v-list-item>
<v-list-item-avatar>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>Kazuya Kojima</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
<v-list-item v-for="(item, index) in items" :key="index" :to='item.link'>
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
</template>
<script>
export default {
data () {
return {
items: [
{ title: 'ホーム', icon: 'mdi-home', link:{name:'home'} },
{ title: '連絡先一覧', icon: 'mdi-menu', link:{name:'addresses'} }
]
}
}
}
</script>
こうする。
 
やったこと
<v-list-item v-for="(item, index) in items" :key="index" :to='item.link'>
 to=の記述を設定
itemのところにリンク先を設定

 

{ title: 'ホーム', icon: 'mdi-home', link:{name:'home'} },
{ title: '連絡先一覧', icon: 'mdi-menu', link:{name:'addresses'} }
 
結果
localhost:8081のページでメニューを開く

 

f:id:kirikko_Scondcube:20200803002246p:plain

メニューから連絡先一覧をクリック

f:id:kirikko_Scondcube:20200803002254p:plain

 

 

localhost:8081/addressesへ遷移するようになった。

f:id:kirikko_Scondcube:20200803002304p:plain

 
 

さらに連絡先追加を押して別ファイルsrc/views/AddressFormへ遷移するようにする

 

src/views/AddressForm.vueを追加する

 

AddressForm.vue

<template>
<v-container text-xs-center>
<v-layout row wrap justify-center>
<v-flex xs12 class="text-center">
<h1>連絡先編集</h1>
</v-flex>

<v-flex xs5 mt-5>
<v-card>
<v-card-text>
<v-form>
<v-text-field v-model="address.name" label="名前"></v-text-field>
<v-text-field v-model="address.tel" label="電話番号"></v-text-field>
<v-text-field v-model="address.email" label="メールアドレス"></v-text-field>
<v-text-field v-model="address.address" label="住所"></v-text-field>
<div class="text-center">
<v-btn @click="$router.push({ name: 'addresses' })">キャンセル</v-btn>
<v-btn color="info" class="ml-2">保存</v-btn>
</div>
</v-form>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>

<script>
export default {
data () {
return {
address: {}
}
}
}
</script>

 

src/router/index.jsに追記

import Vue from 'vue'//vue本体の読み込み
import VueRouter from 'vue-router'//vue Routerのライブラリの読み込み
import Home from '../views/Home.vue'
import Addresses from '../views/Addresses.vue'
import AddressForm from '../views/AddressForm.vue'

Vue.use(VueRouter)//useメソッドにルーターを渡して有効にしている

const routes = [//ここでルートの操作をする処理を記載。各ページに関するルートとそのルートで表示するコンポーネントの設定を書く
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/addresses',
name: 'addresses',
component: Addresses
},
{
path: '/addresses/:address_id?/edit',
name: 'address_edit',
component: AddressForm
},
{
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',//mode: 各画面のパスの設定「history」ではパスを「/」で区切る一般的なやり方を指定している(他にhash, abstractなどがある。)
base: process.env.BASE_URL,//base: アプリケーションのベースとなるパスを指定するもの
routes
})

export default router

 

追加箇所は

import AddressForm from '../views/AddressForm.vue'

↑で新しいページのインポート

{
path: '/addresses/:address_id?/edit',
name: 'address_edit',
component: AddressForm
},

↑で新しいページのパスを追加。

 
:address_id?/edit',

この部分での意味は

:address_id?でアドレスのIDが入る。

IDが1のアドレスだったら「:/addresses/1/edit」が入る想定

このように「:」から始まる文字列をパスに含めることでパスに含まれるIDなどのパラメーターをコンポーネントで受け取ることができる。

これは既存の連絡先を編集する際に編集対象の連絡先を特定するために使用する。

パスの後ろの「?」の意味

?をつけることでパスに含まれるパラメーターをオプションの扱いにしている。

オプションなのでアドレスIDを渡さないパスとして「/addresses/edit」と言うパスの場合にもこのルートが該当するようになる。

こうすることでアドレスIDがないパスでアクセスが来た場合には新規のアドレス追加として扱うようにこれからコードを追加していくことが可能

 

src/views/Address.vueを修正

<template>
<v-container text-xs-center justify-center>
<v-layout row wrap>
<v-flex xs12>
<h1>連絡先一覧</h1>
</v-flex>

<v-flex xs12 mt-5 mr-5 text-right>
<router-link :to="{ name: 'address_edit' }">
<v-btn color="info">
連絡先追加
</v-btn>
</router-link>
</v-flex>


<v-flex xs12 mt-5 justify-center>
<v-data-table :headers='headers' :items='addresses'>
<template v-slot:items="props">
<td class="text-xs-left">{{ props.item.name }}</td>
<td class="text-xs-left">{{ props.item.tel }}</td>
<td class="text-xs-left">{{ props.item.email }}</td>
<td class="text-xs-left">{{ props.item.address }}</td>
</template>
</v-data-table>
</v-flex>
</v-layout>
</v-container>
</template>

<script>
export default {
data () {
return {
headers: [
{ text: '名前', value: 'name' },
{ text: '電話番号', value: 'tel' },
{ text: 'メールアドレス', value: 'email' },
{ text: '住所', value: 'address' }
],
addresses: [
{
name: '友人1',
tel: '090-0000-1111',
email: 'sample1@mail.com',
address: '東京都渋谷区'
},
{
name: '友人2',
tel: '090-2222-3333',
email: 'sample2@mail.com',
address: '東京都品川区'
}
]
}
}
}
</script>

 

修正箇所
 
<v-flex xs12 mt-5 mr-5 text-right>
<router-link :to="{ name: 'address_edit' }">
<v-btn color="info">
連絡先追加
</v-btn>
</router-link>
</v-flex>
を追加
この記述で追加のボタンを表示させ、
先ほどindex.jsで指定したページ名にto=""で遷移するように記述している。
 
結果
連絡先追加を押すと以下の画面に遷移する(AddressForm.vue)

f:id:kirikko_Scondcube:20200803004539p:plain

連絡先追加機能の実装

前述の連絡先フォームを入力し、保存を可能にする
 
 
src/store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
state: {
drawer: false,
addresses:

},
mutations: {
toggleSideMenu(state){
state.drawer = !state.drawer
},
addAddress (state, address) {
state.addresses.push(address)
}
},
actions: {
toggleSideMenu({commit}){
commit('toggleSideMenu')
},
addAddress({ commit }, address){//第二引数でコンポーネントから値を受け取ることができる。コンポーネントでaddadressのアクションを呼び出す際に
commit('addAddress',address)
}
},
modules: {
}
})
追加ポイント
addresses:
mutationsに
addAddress (state, address) {
state.addresses.push(address)
}
actionsに
addAddress({ commit }, address){//第二引数でコンポーネントから値を受け取ることができる。コンポーネントでaddadressのアクションを呼び出す際に
commit('addAddress',address)
}
を追加
addAddressの第二引数でコンポーネントから値を受け取ることができる。コンポーネントでaddadressのアクションを呼び出す際にアドレスを渡すことを想定している。それをミューテーションに渡してAddresses.vueのaddressesの配列に渡すようにしている。
 
 
AddressForm.vue
<template>
<v-container text-xs-center>
<v-layout row wrap justify-center>
<v-flex xs12 class="text-center">
<h1>連絡先編集</h1>
</v-flex>

<v-flex xs5 mt-5>
<v-card>
<v-card-text>
<v-form>
<v-text-field v-model="address.name" label="名前"></v-text-field>
<v-text-field v-model="address.tel" label="電話番号"></v-text-field>
<v-text-field v-model="address.email" label="メールアドレス"></v-text-field>
<v-text-field v-model="address.address" label="住所"></v-text-field>
<div class="text-center">
<v-btn @click="$router.push({ name: 'addresses' })">キャンセル</v-btn>
<v-btn color="info" class="ml-2" @click="submit">保存</v-btn>
</div>
</v-form>
</v-card-text>
</v-card>
</v-flex>
</v-layout>
</v-container>
</template>

<script>
import { mapActions } from 'vuex'

export default {
data () {
return {
address: {}
}
},
methods: {
submit () {
this.addAddress(this.address)
this.$router.push({ name: 'addresses' })
this.address = {}
},
...mapActions(['addAddress'])
}
}
</script>
 
追加点
保存クリックの記述に関数をもたせる
<v-btn color="info" class="ml-2" @click="submit">保存</v-btn>
 
インポート
import { mapActions } from 'vuex'
関数の追加
methods: {
submit () {
this.addAddress(this.address)
this.$router.push({ name: 'addresses' })
this.address = {}
},
...mapActions(['addAddress'])
}
 
 
 
Addresses.vue
<template>
<v-container text-xs-center justify-center>
<v-layout row wrap>
<v-flex xs12>
<h1>連絡先一覧</h1>
</v-flex>

<v-flex xs12 mt-5 mr-5 text-right>
<router-link :to="{ name: 'address_edit' }">
<v-btn color="info">
連絡先追加
</v-btn>
</router-link>
</v-flex>


<v-flex xs12 mt-5 justify-center>
<v-data-table :headers='headers' :items='addresses'>
<template v-slot:items="props">
<td class="text-xs-left">{{ props.item.name }}</td>
<td class="text-xs-left">{{ props.item.tel }}</td>
<td class="text-xs-left">{{ props.item.email }}</td>
<td class="text-xs-left">{{ props.item.address }}</td>
</template>
</v-data-table>
</v-flex>
</v-layout>
</v-container>
</template>

<script>
export default {
created () {
this.addresses = this.$store.state.addresses
},
data () {
return {
headers: [
{ text: '名前', value: 'name' },
{ text: '電話番号', value: 'tel' },
{ text: 'メールアドレス', value: 'email' },
{ text: '住所', value: 'address' }
],
addresses:
}
}
}
</script>
 
追加、修正点
createdの追加
created () {
this.addresses = this.$store.state.addresses
},
 
dataに設定のaddressesの配列の中身を削除。
初期値なしにし、AddressForm.vueでsubmit関数が実行され追加した内容がプッシュされるようにする
addresses: