状態管理方法をStoreパターンからVuexに移行した理由


はじめに

事故マップというサービスを個人開発しています。

機能エンハンスに伴い、コンポーネント間のデータ共有方法(状態管理方法)をStoreパターンからVuexに変更しました。
その変更理由について紹介したいと思います。

Storeパターンとは

公式で紹介している状態管理のテクニックです。
概要はstoreというグローバルなインスタンスを用意して、そこに各コンポーネントが参照したい値を格納する方法です。

Vuexとは

VuexとはVue.js アプリケーションのための 状態管理パターン + ライブラリです。
Fluxというデータ管理方法をベースにしています。イメージ的にはほぼStoreパターンと同じですが、VuexはStoreパターンに格納するデータの生成や更新ロジックも持たせたものだと思って貰えれば良いと思います。

Storeパターンだと何が困るか

一言で言うと、「storeが保持する値は各コンポーネントでデータバインディングされない」ためです。

公式でStoreパターンのデータサイクルを上の図のように説明をしていますが、storeに格納した値の変更はデフォルトでは各コンポーネントに伝わらないため「Trigger Updates」は実質動きません。

具体的な例

データを格納するstore.jsとそれを利用するsample.vueで説明をします。

store.js
export default {
    greeting : Hello,

    setGreeting(newValue) {
            this.greeting = newValue
    },
}
sample.vue
<template>
    <p @click=“changeValue()”>{{greetingValue}}</p>
</template>

import store from "@/store.js"

export default {
    data() {
        return {
            greetingValue : store.greeting // storeに格納した値は一応表示されるが、それは初期値として渡しているだけ
        }
    },
    methods: {
        changeValue() {
            store.setGreeting(“Good Afternoon”)   // この変更はDOMに反映されない
        }
    }   
}

storeで保存したgreetingという値をsample.vue側で表示をします。
静的な値であれば問題ありませんが、やりたいのは状態管理であるためデータの更新とその値の反映は当然想定されます。
しかし、このような実装だとchangeValue()が呼ばれたとしても値がDOMに反映されることはありません。
何故ならばstoreはVueの世界の中で監視対象ではないためです。

ただ方法は全く無いわけではなく、mugi_unoさんのVue.observable & TypeScriptで手早く安心できる状態管理を手に入れるで紹介しているVue.observableを使うことでstoreを監視対象に含める事ができます。

storeを監視対象に含めた場合の例 (sample.vueの変更のみ)

sample.vue
<template>
    <p @click=“changeValue()”>{{greetingValue}}</p>
</template>

import store from "@/store.js"
import Vue from "vue"

var state = Vue.observable(store)

export default {
    computed: {
        greetingValue() {
            return state.greeting
        }   
    },  
    methods: {
        changeValue() {
            store.setGreeting(“Good Afternoon”) 
        }
    }   
}

observableでstoreを監視対象に含める事でchangeValue()内での変更がVueに伝わるようになります。
さらにcomputedでstore.greetingの値を渡す事によって、その変更をDOMに反映させる事ができるようになります。

Storeパターン+Vue.observableで全て解決という訳にはいかない

当初の課題は解決されますが、これはこれで別の課題が浮上してきます。

storeパターンはデータの入れ物です。大抵入れるデータの生成処理を行うのは、各コンポーネントになると思います。
また、恐らく状態管理にStoreパターンを使う背景として各コンポーネント間のデータ連携をpropやemitに頼らない方法で実現したいためだと思いますが、結局それらに頼らざるを得ないケースが生まれてきます。

例えば、コンポーネントAとBがあり、下記の役目を持っているとして

  • Aは表示データを生成し、storeに格納を行う
  • Bはstoreに格納された値を表示し、ユーザーからの入力を受け付ける

Bがユーザーの入力値をに基づいて表示データを更新したいとした場合どうすれば良いでしょうか?

データの生成処理はAの内部メソッドであるため、BからAに対して値を渡してやらなければいけなくなるはずです。
これはstoreで疎にしたはずの結合が結局emitなどで強く結びついてしまいます。

ならば、storeにデータ生成ロジックを持たせようというのが次の考えですが、
ここまで来るととそれVuexやでという話になります。

Vuexの良いとこ悪いとこ

私はこれらの理由でVuexを採用して良かったと思っていますが、良いところもあれば悪いところもあります。

良いところ

  • Vuexに則れば肥大化したコンポーネントをモデルとモデルヴューに切り離す事ができ、コンポーネント間の依存はほぼ排除する事ができる
  • 自然とアプリの構成がMVVMとなるため、全体の見通しが良くなる

悪いところ

  • MVVMを理解していないと学習コストが高い
  • module(データモデル)の切り方に悩むし、うまく設計できないと逆に煩雑になる

実際、Vuexを導入してリファクタリングした結果、ビジネスロジックmethodだらけだった各コンポーネントの行数は約4割程度削減する事ができました。
Vuexを導入するか迷っている方の参考になればと思います。