Vuexでシステム日時をリアクティブに　＋　Vuexプラグイン自作時の注意点


Vueを使っていて、画面上で日時を出したい場合や日時をもとに比較したい場合があったが、それに対してベストなプラグインが無かったので自作しました。
[vuex-systime npm](https://www.npmjs.com/package/vuex-systime)
[vuex-systime github](https://github.com/funkjk/vuex-systime)


#日付をシンプルにリアクティブにできない理由
JavaSriptでローカルの日時を取得する方法は以下のように実装します。

```javascript
const currentDate = new Date() // 日付型を取得
const currentTime = Date.now() // エポックタイムからの経過ミリ秒を取得
```

これをVueのcomputedに書いたとします

```javascript
computed: {
  currentDate() {
    const d = new Date()
    return d
  }
}
```

ただ、これではリアクティブにはならないです。理由は[ここらへん](https://ryotah.hatenablog.com/entry/2018/08/30/211541)とか見て欲しいですが、VueのリアクティブはVueで管理されているプロパティが変わったら、依存しているプロパティを再計算しに行く形で実装しています。そこでこの日付はnew Date()を実行すれば変わるのですが、実行するまでは変わらないので変わったことを検知されないので、初期化時に計算された値がキャッシュされ、そのまま保持されます。

##日付をリアクティブにするやり方
よくあるやり方はsetInterva()で都度書き換えて上げれば良いです。

```javascript
data: {
  currentDate:null
},
mounted() {
  setInterval(() => {
    this.currentDate = new Date()
  }, 1000)
}
```

１秒毎にcurrentDateが変わります。そのためそれをもとに画面描画しているとその値が常に再描画されていきます。
[vuex-now](https://www.npmjs.com/package/vuex-now)というライブラリがあり、vuexのプラグインとして設定しておくと良い感じに１秒毎に描画してくれます。

##自作した理由
vuex-nowでだいたい事足りそうなのですが、以下の理由から自作しました。

 - １秒毎の書き換えだと書き換わりすぎる（１分毎とかが良い）
  - 現在時刻と比較して、何かするといのがあるが、現在時刻との比較する量が多く性能が懸念された
  - １分毎なんだけど、１分のインターバルではなく、00秒で切り替えたい
 - サーバの時間に基づいていて欲しい。サーバの時間とクライアントの時間がずれていた場合にはその差分を考慮して計算して欲しい。
 - vuexのプラグインを作ってみたかった

１つ目の理由は、秒を切り捨てたプロパティを別途保持しておき、そいつと比較すれば良さそう。
２つ目の理由も、vuex-nowを使って、それとサーバ時間との差分を計算した別のプロパティを持てば良さそう。
結局３つ目の理由なのかもしれない。


##Vuexプラグイン作成
以下の機能を保持したVuexプラグインを作りました

 - リアクティブにシステム日時を保持
 - 更新タイミングを指定可能
 - １分ごとに更新といった場合には、００秒が経過したタイミングで書き換わる
 - サーバ時刻とクライアント時刻の差分（オフセット時間）を保持し、オフセット時間とクライアント時間をもとにシステム日時を算出する

Vuexで任意の機能を持ったStoreを登録する場合には、対象のstoreをimportして登録するでも良いのですが、プラグインを使っても登録できます。
以下はVuexのプラグインでRegisterModuleでstoreを登録する際の記載です。

```javascript
import {systemDatetimeStore} from './vuex-systime-store'

export default function VuexSystemDatetime (param) {
  return store => {
〜省略〜
    // モジュールの登録
    store.registerModule(moduleName, systemDatetimeStore)
    // 登録後に、必要に応じて初期化処理等を走らせる
    store.dispatch(moduleName + '/updateTime', null, { root: true })
  }
}
```

モジュール名は任意にプラグインの中で任意に決めても良いのですが、paramから上書きできるようにした方が良いと思います。

また、再利用性を求めてStoreの中で、どのモジュール名が与えられているか取得しようとしたのですが、現在はできないようですね。仕方ないので、モジュール名をstateに持ってしまいました。そして、モジュール名は、プラグインからの初期化時に設定という形です。
何か良い案があれば良いのですが。。。

```javascript
export const systemDatetimeStore = {
  namespaced: true,
  state: {
    moduleName: null, // モジュール名を持たせてしまっている
```

>１分ごとに更新といった場合には、００秒が経過したタイミングで書き換わる

という処理については、以下のアクションで実装しました。

```javascript
    updateTime: function ({state, commit, dispatch}, param) {
      const now = Date.now()
      const calcTime = now + state.offsetTime
      // cutoffTimeに60*1000を持たせておき、差異を算出
      const timeoutTime = calcTime % state.cutoffTime

      // setTimeoutで差異時間経過後に再度自身を再帰的に呼び出す
      const oldTimeoutId = moduleParameter[state.moduleName].timeoutID
      moduleParameter[state.moduleName].timeoutID = setTimeout(() => {
        dispatch('updateTime')
      }, state.cutoffTime - timeoutTime)
      // もし別のsetTimeoutが呼び出されているならば、リセット
      clearTimeout(oldTimeoutId)

      commit('setLocalTime', now)
      commit('setSystemTime', calcTime)
    }
```

##感想

Vuexのプラグインって、stateも含めて再利用したい場合にも応用できるみたいですね。Vueライブラリと合わせて提供すればコンポーネントおよびその状態管理も合わせて提供可能になりそうですね。


- https://www.npmjs.com/package/vuex-systime
- https://github.com/funkjk/vuex-systime