Nuxt.jsでウェブストレージを扱う

# はじめに
ウェブストレージ便利ですよね、特にローカルストレージはCookieが使えないPWAや、DBを用意するほどでもないちょっとしたデータを保存したいときに使うことが多いのではないかと思います。

ただ、いざウェブストレージのデータを扱おうと実装を進めるとデータの同期や保存のタイミング等でつっかかることがままあります。
そこで今回はNuxt.jsでウェブストレージを簡単に扱う手法を考えましたので簡単にご紹介しようと思います。

# 実装方針
コンポーネントやページから直接ウェブストレージにアクセスしていると処理やデータが散乱してしまい辛い状況に陥りやすいです。
そこで、ウェブストレージの参照や処理は全てストアに一本化します。
各コンポーネントやページは共通のステートやアクションを参照することになりますのでデータが非常に扱いやすくなります。


# 実装
入力した値をウェブストレージに保存する簡単なデモを作成します。
今回はローカルストレージを使用しますがセッションストレージでも同様の方法で対応可能です。

## ウェブストレージ操作用のクラスを作成
1つのウェブストレージを扱うだけならクラスを作るまでもないのですが、今回は複数のウェブストレージを管理することを前提に作成しておきます。
実装例ですので登録・削除する機能だけ用意してあります。

```javascript:lib/storage.js
export default class {
  constructor(key) {
    this.key = key
    this.data = []
  }
  init() {
    this.data = JSON.parse(localStorage.getItem(this.key)) || []
  }
  save() {
    localStorage.setItem(this.key, JSON.stringify(this.data))
  }
  regist(payload) {
    this.data.push(payload)
    this.save()
  }
  remove(payload) {
    this.data = this.data.filter(el => el !== payload)
    this.save()
  }
}
```

## クライアント起動時のプラグイン作成
クライアント起動時にウェブストレージのデータをストアへ注入する必要があるのですが、Nuxtのストアにはクライアント起動時の関数は用意されていませんのでその機能を作成します。
Nuxtのデフォルトにあるアクション「nuxtServerInit」にならって「nuxtClientInit」という名称で管理します。

```javascript:plugins/nuxt-client-init.js
export default ({ store }) => {
  for (const key in store._actions) {
    if (key.match(/nuxtClientInit/)) {
      store.dispatch(key)
    }
  }
}
```
```javascript:nuxt.config.js
plugins: [
  { src: '~/plugins/nuxt-client-init', mode: 'client' }
]
```
### `nuxt-client-init-module`を使う場合
`nuxt-client-init-module`というモジュールが既にあります。このモジュールはルートストアに「nuxtClientInit」というアクションを用意するものですので、必要なアクションをルートストアで動かしてください。

```terminal
$ yarn add nuxt-client-init-module
```

```javascript:nuxt.config.js
modules: ['nuxt-client-init-module']
```

```javascript:store/index.js
export const actions = {
  nuxtClientInit({ dispatch }) {
    dispatch('module/nuxtClientInit')
  }
}
```

## ストア実装
ストアのアクションはあくまでインスタンスの関数を実行し、その結果をステートへ返すだけのシンプルな設計とします。
アクションに直接ウェブストレージ周りの機能を実装してもよいのですが、複数ウェブストレージを扱ったり機能が複雑になってくると煩雑になりやすいので、ウェブストレージの処理とストアの処理を明確に分担するように設計しています。

```javascript:store/message.js
import LocalStorage from '~/lib/storage'

const storageMessage = new LocalStorage('message')

export const state = () => ({
  data: []
})

export const mutations = {
  dataUpdate(state, payload) {
    state.data = [...payload]
  }
}

export const actions = {
  nuxtClientInit({ commit }) {
    storageMessage.init()
    commit('dataUpdate', storageMessage.data)
  },
  regist({ commit }, payload) {
    storageMessage.regist(payload)
    commit('dataUpdate', storageMessage.data)
  },
  remove({ commit }, payload) {
    storageMessage.remove(payload)
    commit('dataUpdate', storageMessage.data)
  }
}
```

## 完成
あとは通常のストアと同様に利用できます。

```vuejs:pages/index.vue
<template>
  <div>
    <form @submit.prevent="regist()">
      <input type="text" v-model="message">
      <button type="button" @click="regist()">
        登録
      </button>
    </form>
    <ul>
      <li v-for="(item, index) in messageData" :key="index">
        {{ item }}
　　　　　<button type="button" @click="remove(item)">
            削除
         </button>
      </li>
    </ul>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  data() {
    return {
      message: ''
    }
  },
  computed: {
    ...mapState({
      messageData: state => state.message.data
    })
  },
  methods: {
    regist() {
      if (this.message) {
        this.$store.dispatch('message/regist', this.message)
        this.message = ''
      }
    },
    remove(payload) {
      if (window.confirm('削除しますか？')) {
        this.$store.dispatch('message/remove', payload)
      }
    }
  }
}
</script>
```

# おまけ（Vueインスタンス版）
小規模でストア用意するほどでもないよって人向けにプラグインでVueインスタンスへ注入する方法も記載しておきます。
以下のデモでは上記で使ったクラスを流用してください。

## プラグインを作成する
Vueインスタンスへ注入するプラグインファイルを作成します。

```javascript:plugins/storage.js
import Vue from 'vue'
import LocalStorage from '~/lib/storage'

Vue.prototype.$storageMessage = new LocalStorage('message')
Vue.prototype.$storageMessage.init()
```
```javascript:nuxt.config.js
  plugins: [
    { src: '~/plugins/storage', mode: 'client' }
  ]
```

## 完成
設定はこれだけで後はページやコンポーネントからウェブストレージのデータを操作できます。

```vuejs:pages/index.vue
<template>
  <div>
    <form @submit.prevent="regist()">
      <input type="text" v-model="message">
      <button type="button" @click="regist()">
        登録
      </button>
    </form>
    <ul>
      <li v-for="(item, index) in messageData" :key="index">
        {{ item }}
　　　　　<button type="button" @click="remove(item)">
            削除
         </button>
      </li>
    </ul>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  data() {
    return {
      messageData: [],
      message: ''
    }
  },
  mounted() {
    this.update()
  },
  methods: {
    update() {
      this.messageData = this.$storageMessage.data
    },
    regist() {
      if (this.message) {
        this.$storageMessage.regist(this.message)
        this.update()
        this.message = ''
      }
    },
    remove(payload) {
      if (window.confirm('削除しますか？')) {
        this.$storageMessage.remove(payload)
        this.update()
      }
    }
  }
}
</script>
```

# さいごに
ウェブストレージのデータを更新するときに「ウェブストレージにあるデータ」と「ウェブストレージから引っ張ってきたデータ」と２つを管理する必要があるのでそこがもたつきやすいですよね。
今回のように「ウェブストレージ←→ストア←→ページ・コンポーネント」という形で一方向の処理で管理すると、土台さえ作ってしまえばウェブストレージ周りの処理を意識せずに扱えるのでオススメです。
大した手法ではないのですがウェブストレージの実装で手をこまねいている方は参考にしていただければ幸いです！