Vuexでデータの永続化、IE対応、ESLintの使い方

# 1. 概要
今回はvuexでのデータ管理とvuex-persistedstateを使ったフロントエンドでのデータ永続化について説明します。 vuex-persistedstateを使うとデータはブラウザの[localStorage](https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API)を使って永続化されブラウザのリロード時もフォームの入力結果などを保持してくれるようになります。
また、デバックしやすいように[ChromeのVueプラグイン](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd?hl=ja)の事前インストールを推奨しています。

# 2. 前提条件
[Railsにvueをインストール](https://qiita.com/MariMurotani/items/f040e3ce23ece18d50df)のチュートリアルを終了してフロントエンド開発に慣れておきましょう。

# 3. インストール
```
yarn add vuex vuex-persistedstate
```

# 4. コーディング
Vue.jsのチュートリアルを最初から学習した場合に[storeパターン](https://jp.vuejs.org/v2/guide/state-management.html)を利用してアプリケーション内でデータストアの一意性を保持するようにという概念について学ぶのですが、こちらはデザインパターンの話であり実際には[vuex](https://vuex.vuejs.org/ja/)というデータストアライブラリを使って開発する事をお勧めします。
まず、store.jsというファイルを作成して下記のライブラリをimportします。

## 4.1 データストアの作成とVueインスタンスへの登録
```store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
```

js内でVueコンポーネントを生成する際にstoreに読み込んだVuex.StoreをVueオブジェクト生成時にstoreに指定します。こうすることで、生成されたVueインスタンスの中で`this.$store`という指定方法でstoreのデータを参照したり機能を呼び出したりする事ができるようになります。

```application.js
import Vue from 'vue'
import App from '../app.vue'
import store from './store.js'

document.addEventListener('DOMContentLoaded', () => {
  new Vue({
    el: '#app',
    store: store,
    router,
    render: (h) => h(App)
  })
})
```

store.jsに機能を追加していきます。

```store.js

export default new Vuex.Store({
  state: {
    count: 0
  },
  getters: {
    getCounter: function(state){
      return state.count
    }
  },
  mutations: {
    increment (state) {
      console.log("mutations > increment is called")
      state.count++
    },
    assign: function(state, args){
      console.log("mutations > assign is called")
      state.count = args.amount
    }
  },
  actions: {
    increment (context) {
      console.log("action > increment is called")
      console.log(context)
      context.commit('increment')
    },
    assignValue(context,arg1) {
      console.log("assignValue")
      console.log(arg1.amount)
      console.log(context)
      context.commit({
        type: 'assign',
        amount: arg1.amount
      })
    }
  }
})
```


`new Vuex.Store()`に記載するオプションについては、
[Vuex.Store コンストラクタオプション](https://vuex.vuejs.org/ja/api/#vuex-store-%E3%82%B3%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%BF%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3)に全てのオプションが記載されています。今回は代表的な機能について確認します。


- [State](https://vuex.vuejs.org/ja/guide/state.html) 
データを格納しておく箱です。Vuexインスタンス作成時に指定します。

- [Getter](https://vuex.vuejs.org/ja/guide/getters.html) 
stateに格納したデータを取り出すメソッドを定義していきます。[Vuex.Store インスタンスプロパティ](https://vuex.vuejs.org/ja/api/#vuex-store-%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3)を参照しましょう。

- [Mutation](https://vuex.vuejs.org/ja/guide/mutations.html) 
storeのデータを操作するロジックを定義していきます。

- [Action](https://vuex.vuejs.org/ja/guide/actions.html)
mutationで定義したメソッドを利用して変更をコミットするロジックを定義していきます。


上記のサンプルでは、stateにcounterという変数を定義して、mutationsのincrementメソッドで値を変更、actionsのincrementメソッドで実際のデータをコミットしています。

## 4.2 データストアの呼び出し
アクションの`increment`を呼び出します。
[vuexインスタンスメソッド](https://vuex.vuejs.org/ja/api/#vuex-store-%E3%82%A4%E3%83%B3%E3%82%B9%E3%82%BF%E3%83%B3%E3%82%B9%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89)というのが定義されておりstoreインスタンスから呼び出しが可能になっています。よく使う2つのメソッドについて紹介します。

### action経由での呼び出し
```javascript 
store.dispatch('increment')
```

### mutationのincrementメソッド直接呼び出し
```javascript 
store.commit({
  type: 'increment',
  amount: 10
})
```

stateのcounterの値を直接書き換える事はできるのですが`store.commit`をするまで変更は適用されません。Vueインスペクタを開いて下記のサンプルを実行して、値の変化を確認しながらボタンを押してみましょう。一番右の「updateWithoutCommit」では画面の値が更新されているにも関わらず、Vuexのモニターの中身がコミットされていない事が分かります。

```test.vue
<template>
  <div>
    <div>
      this.$store.state.count: {{get_value_directory}}<br>
    </div>
    <div>
      get_value_via_getter: {{get_value_via_getter}}<br>
    </div>
    <button v-on:click="incrementValueViaDispatch">incrementValueViaDispatch</button>
    <button v-on:click="upateWithSpecificValue">upateWithSpecificValue</button>
    <button v-on:click="updateValueWithMutation">updateValueWithMutation</button>
    <button v-on:click="updateWithoutCommit">updateWithoutCommit</button>
  </div>
</template>
<script>
  export default {
    name: 'page_test',
    props: {

    },
    components: {

    },
    data: function () {
      return {
        isActive: false
      }
    },
    mounted: function (event) {
    },
    computed: {
      get_value_directory: function() {
        return this.$store.state.count
      },
      get_value_via_getter: function() {
        return this.$store.getters.getCounter
      }
    },
    methods: {
      incrementValueViaDispatch: function(){
        this.$store.dispatch('increment')
      },
      upateWithSpecificValue: function(){
        this.$store.dispatch({
          type: 'assignValue',
          amount: 10
        })
      },
      updateValueWithMutation: function(){
        this.$store.commit({
          type: 'assign',
          amount: 20
        })
      },
      updateWithoutCommit(){
        this.$store.state.count = 100
      }
    }
  }
</script>
<style scoped lang="scss">

</style>
```

## 4.3 データの永続化

データの永続化をするにあたってcreatePersistedStateをpluginsオブジェクトに追加する事でlocalStorageを利用したデータの永続化が実現できます。storeオブジェクト生成時にpluginsの値を指定します。

```javascript:store.js
import createPersistedState from "vuex-persistedstate";

export default new Vuex.Store({
  plugins: [createPersistedState()]
})
```

<img width="1438" alt="スクリーンショット 2019-02-16 15.53.27.png" src="https://qiita-image-store.s3.amazonaws.com/0/89997/1452c2ce-fd86-8294-7604-3e27c796f494.png">

インスペクタでlocalStorageの中身を参照するとvuexというキーでstoreのstateのオブジェクトがリアルタイムに保存されていく状態が確認できます。

# * IE対応
今回、IEに対応するという特別な要件がありましたが、こちら[webpack](https://webpack.js.org/concepts/)を使っている場合には一行でサクッと解決です。
[Babel](https://www.npmjs.com/package/Babel)を使ってhtml/sass/jsなどをコンパイルしていると開発者自身がIE対応のタグかどうかを意識してコードを書くことは難しいのですが、[babel-polyfill](https://www.npmjs.com/package/babel-polyfill)というライブラリを追加する事で一瞬で解決してしまいます。ブラウザごとの書き分けなどはpolyfillにお任せして綺麗なES6記法のコードを書くことに集中できますね :-D

```
yarn add babel-polyfill
```

# * ESLintの使い方
ES6記法に慣れないという問題もあり、[ESLint](https://eslint.org/docs/user-guide/getting-started)の構文チェックを導入しています。

- 構文チェック
  .vueファイルを解析して構文が正しいかどうかチェックします
[eslint](https://www.npmjs.com/package/eslint), [eslint-plugin-vue](https://vuejs.github.io/eslint-plugin-vue/user-guide/), [vue-eslint-parser](https://www.npmjs.com/package/vue-eslint-parser)

- Gitフック
[ghooks](https://www.npmjs.com/package/ghooks), [husky](https://www.npmjs.com/package/husky)

開発環境でしか利用しないので、--devを付与してdevDependencies以下に追加されるようにします。
gitコマンドのcommitの直前の動作で構文チェックを実行しています。
`eslint`の実行時に`--fix`オプションを付与する事で自動で修正してくれます。

```
yarn add --dev eslint eslint-plugin-vue vue-eslint-parser ghooks husky
```

作成されるpackage.jsonは下記のようになるので確認しておきましょう。

```json:package.json
{
  "name": "***************",
  "private": true,
  "dependencies": {
    "@rails/webpacker": "https://github.com/rails/webpacker",
    "axios": "^0.18.0",
    "babel-polyfill": "^6.26.0",
    "eslint": "^5.14.0",
    "vue": "^2.5.21",
    "vue-eslint-parser": "^5.0.0",
    "vue-loader": "^15.4.2",
    "vue-router": "^3.0.2",
    "vue-template-compiler": "^2.5.21",
    "vuex": "^3.0.1",
    "vuex-persistedstate": "^2.5.4"
  },
  "devDependencies": {
    "babel-eslint": "^10.0.1",
    "eslint-plugin-vue": "^5.2.1",
    "ghooks": "^2.0.4",
    "husky": "^1.3.1",
    "webpack-dev-server": "^3.1.14"
  },
  "scripts": {
    "precommit": "./node_modules/.bin/eslint --fix app/frontend/packs/"
  }
}
```

[gon](https://rubygems.org/gems/gon)というRailsのgemを利用してバックエンド側のデータを一部ページ内に埋め込んでいたり、こちらのグローバル変数が`error  '****' is not defined  no-undef`というエラーになってしまいます。エラー抑止のために.eslintrc.jsの設定をします。globalsに変数チェックの有無を定義する事ができます。
`.eslintignore`というファイルでチェックの除外ファイルも指定できるのでこちらの設定も覚えておくと便利です。

```.eslintrc.js
module.exports = {
  "extends": "vue",
  "globals": {
    "gon": true,
    "window": true
  }
};
```

# おまけ
**railsで利用するenvをvueJSフレームワーク内で設定する方法**
https://github.com/rails/webpacker/blob/master/docs/env.md