【VUEX】ローカルステージに要素を保存、削除する方法【初心者向け】

# 開発環境
MacBook Air (M1, 2020)
vue: "^2.6.14",
yarn 1.22.19

# 本記事の内容
本記事では、Vue CLIを使用していますが、そのインストール方法に関しては、言及しません。

本記事では以下の内容について記載します。

①stateの値を変更する方法
②ローカルステージに保存する方法
③ローカルステージから削除する方法

# 準備
* アプリを作成
* Vuexのインストール

## アプリを作成
上記の内容についてhandson形式で説明するため、まずは簡単なアプリケーションを作成します。
入力したフォームをリスト形式で反映してくれるアプリです。
以下のコードをコピペしてください。
```vue:PetView.vue
<template>
    <div>
        <label for="name">Name</label><br>
<!--inputした内容でvueインスタンスの内容を更新したいため、v-modelを使用する-->
        <input id="name" type="text" v-model="name"><br>
        <label for="age">Age</label><br>
        <input id="age" type="number" v-model="age"><br>
        <label for="birthday">Birthday</label><br>
        <input id="birthday" type="date" v-model="birthday"><br>
<!--ボタンをクリックしたら、上記に入力した内容を配列にpushさせる。
        <button @click="savePet">ADD</button>
        <hr />
        <table>
            <thead>
                <th>Name</th>
                <th>Age</th>
                <th>Birthday</th>
            </thead>
            <tbody>
<!--配列に関しては、stateから取得してくる必要がある。これから説明するため、あえて空欄にしておく。-->
                <tr v-for="(pet, index) in " :key="index">
                    <td>{{pet.name}}</td>
                    <td>{{pet.age}}</td>
                    <td>{{pet.birthday}}</td>
                </tr>
            </tbody>
        </table>
    </div>
</template>
<script>
    export default{
        name:"PetView",
//pushしたい内容を定義。push先の配列は、stateで管理するため、記載しない。
        data: function() {
            return {
//inputで入力した値がここに入る。
                name:"",
                age:null,
                birthday:""
            }
        },

        methods: {
            savePet: function() {
                const pet = {
//thisを使用することで、vueインスタンスのプロパティにアクセス。
                    name: this.name,
                    age: this.age,
                    birthday: this.birthday,
                }
            }
        }
    }
</script>
```

## Vuexをインストール
手順は以下です。
* インストール
* javascriptファイルの作成
* main.jsに追記

## Vuexをインストールする。
```:package.json
"vuex": "^3.6.2",
```
記載したら、yarn installする。

## javascriptファイルの作成
ファイル名はなんでも良いが、ここではstore.jsとしておく。
```javascript:store.js
import Vue from 'vue'
import Vuex from 'vuex'
//Vuexの使用を宣言
Vue.use(Vuex)
//全体で使えるように、export
export default new Vuex.Store({
//このVue.jsのプロジェクト全体で使えるグローバル変数を表す。
    state: {
    },
//グローバル変数を加工する算出プロパティ。stateを加工する際はここで行う。
    getters: {
    },
//stateの値を変更することを許された唯一の機能。
    mutations: {
    },
//mutationが同期処理しかできないため、非同期処理を行う機能。しかし、予測可能性を高めるため、ここでは、mutationを呼ぶ際に使う。
    actions: {
    }
})

```
## main.jsに追記
```javascript:main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store/index';//追記

Vue.config.productionTip = false

new Vue({
  store,//追記
  render: h => h(App),
}).$mount('#app')
```

# ローカルステージに保存する方法
ローカルステージに保存する方法の前に、stateというグローバル変数に値を保存、変更する方法について解説します。
①stateの値の設置方法
②mutationsでstateの値を変更する方法
③actionsでmutationsを実行する方法
④vueファイル側からactionsを呼び込む方法
⑤gettersで値を加工する方法
⑥加工した値をvueに反映する方法

順に説明していきます。

# stateの値の設置方法 
```javascript:store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        pets:[] //追記
    },
    getters: {
    },
    mutations: {
    },
    actions: {
    }
})

```
stateには基本的に全体で管理したい情報を設置します。
自分の場合はpetsという配列を管理したかったので、このような値を設置しました。

## mutationsでstateの値を変更する方法
mutationsとはstateの値の変更を許された唯一の機能です。
methodsの方でstateの値を変更することはできるのですが、コードの可読性を上げるために、そう決めます。

今回自分が行いたいのは、petsという配列にフォームで入力した値をpushするということです。
以下のようなコードを記載してください。
```javascript:store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        pets:[] //追記
    },
    getters: {
    },
    mutations: {
    },
    actions: {
    }
```
①stateの状態を変更したいので、第一引数にはstate,第二引数には先ほどPetView.vueファイルで定義したpetを取ります。

ちなみに第二引数に複数の引数を与えたい場合は、オブジェクトの形で与えてあげます。

②stateのオブジェクトの中のpetsという配列を取りたいので、このように書きます。

## actionsでmutationsを実行する方法
mutationsに行いたい処理を書いただけでは実行されません。
その実行を指示する必要があります。

そこで登場するのがactionsです。
しかし、このactions、非同期処理を行うための機能であり、今回のような同期処理を実行させたい場合、使う必要がありません。
しかし、理解度を上げるため、かつコードの可読性を高めるため、actionsをmutationsを呼ぶための機能として使います。
```javascript:store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        pets:[] //追記
    },
    getters: {
    },
    mutations: {
        //以下①で説明
        savePet(state, pet) {
            //以下②で説明
            state.pets.push(pet)
        },
    },
    actions: {
    }
```
①第一引数としてはcontextを取ります。
contextにはいろんなものが入っています。commit,state,dispatchなどです。
しかし、ここではactionsはmutationsを呼ぶものとして決めましたので、commitを選択します。

②commitを書くことで、mutationsに定めた処理を実行させることができます。
引数はmutationsに与えたい引数を与えてあげます。

ちなみに余談ですが、このように書くことで、contextを省略できます。
```javascript:store.js
export default new Vuex.Store({
    mutations{
        savePet({commit}, pet){
            commit('savePet', pet);
        }
    }
})
```


## vueファイル側からactionsを呼び込む方法
```vue:PetView.vue
methods: {
    savePet: function() {
        const pet = {
            name: this.name,
            age: this.age,
            birthday: this.birthday,
        }
        //追記
        this.$store.dispatch('savePet', pet)
    },
},
```
追記コードに関しては、こういうものと覚えてください。
actionsを呼ぶ場合は、dispatchになりますが、mutationsを呼ぶ場合は、$storeまではそのままで、commitとなります。

## gettersで値を加工する方法
変更された値をさらに加工したい場合に、使用する機能です。
今回のようにデータを加工する必要がない場合に使う必要はありませんが、今回は理解度を高めるため、あえて、gettersからデータを取得するようにします。

```javascript:store.js
export default new Vuex.Store({
    getters: {
        pets: state => state.pets
    }
}

```
petsの部分は関数で、引数としてstateをとっています。
今回はデータを加工しないので、取得した値をそのままにしておきます。

## 加工した値をvueに反映する方法
stateから呼ぶこともできますが、今回はgettersに任せたので、gettersから値を呼び込みましょう。

可読性を高めるために、編集箇所のみを切り抜いて記載しています。
```vue:PetView.vue
<template>
    <table>
        <thead>
            <th>Name</th>
            <th>Age</th>
            <th>Birthday</th>
        </thead>
        <tbody>
            <!--computedで定義した関数からv-forで値を一つずつ取り出す。-->
            <tr v-for="(pet, index) in arrayPets" :key="index">
                <td>{{pet.name}}</td>
                <td>{{pet.age}}</td>
                <td>{{pet.birthday}}</td>
            </tr>
        </tbody>
    </table>
</template>
<script>
    computed:{
        arrayPets(){
            //petsの部分にはgettersで定義した関数を入れる。
            return this.$store.getters.pets
        }
    }
</script>
```
無事入力した値が反映されていれば成功です。
ちなみに、stateから値を取りたい場合は、gettersをstateに変更すれば取得できます。


# ローカルステージに保存する方法
さて、ここまでで、stateの値を変更することはできるようになりました。
ここからはローカルステージに保存する方法を説明します。
```javascript:store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)
//追記
//localStorageに保存したリストを取得
const savedPets = localStorage.getItem('practice')
//編集
const store = new Vuex.Store({
//編集
//localStorageに保存されたリストがあれば取得、なければ空の配列を返す。
    state: {
        pets: savedPets ? JSON.parse(savedPets): []
    },
    getters: {
        pets: state => state.pets
    },
    mutations: {
        savePet(state, pet) {
            state.pets.push(pet)
        }
    },
    actions: {
        savePet({commit}, pet){
            commit('savePet', pet);
        }
    }
})
//追記
//subscribeはストアのインスタンスメソッドで、全てのmutationの後に呼ばれる。
store.subscribe((mutation, state) => {
//データの状態を更新後にlocalStorageへデータの状態を保存
    localStorage.setItem('practice', JSON.stringify(state.pets))
})
//追記
//main.jsでインポートできるように設定
export default store
```

# ローカルステージから削除する方法
ボタンを押したら押したテーブルが削除される実装行います。
* mutationsとactionsで削除の処理を実行
* vueファイル側でactionsを呼び込む

## mutationsとactionsで削除の処理を実行
```javascript:store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const savedPets = localStorage.getItem('simple-app')

const store = new Vuex.Store({
    state: {
        pets: savedPets ? JSON.parse(savedPets): []
    },
    getters: {
        pets: state => state.pets
    },
    mutations: {
        //indexの要素を一つ削除する処理を記載。
        deletePet(state, index) {
            state.pets.splice(index, 1)
        }
    },
    actions:{
         deletePet({commit}, index){
            commit('deletePet', index);
        }
    }
})

store.subscribe((mutation, state) => {
    localStorage.setItem('simple-app', JSON.stringify(state.pets))
})

export default store
```
##  vueファイル側でactionsを呼び込む
```vue:PetView.vue
<template>
    <tbody>
        <tr v-for="(pet, index) in arrayPets" :key="index">
            <td>{{pet.name}}</td>
            <td>{{pet.age}}</td>
            <td>{{pet.birthday}}</td>
            //追記
            ボタンを押した要素のインデックスのデータを渡してあげる。
            <button @click="deletePet(index)">X</button>
        </tr>
    </tbody>
</template>
<script>
    methods:{
        deletePet: function(index) {
            if(confirm('本当にこのリストを削除しますか？')){
                //actionsで定義した名前を記載し、actionsを呼び込む
                this.$store.dispatch('deletePet', index)
            }
        },
    }
</script>
```

# 終わりに
いかがでしたでしょうか。
次はもっと複雑なアプリも作ってみようと思います！