メインへジャンプ

ushironoko.me

考えるVuex

2020/12/28 ⏳ 4 min read

Vuexって何が良いんだっけ/今なんで使い続けてるんだっけを考えていたらややこしくなってきたのでここに全部まとめる。実は数年前から各所で散々言われている話なので今更感が強い。

Vuexを使う理由

公式に完璧に表現されているので引用

Vuex は Vue.js アプリケーションのための 状態管理パターン + ライブラリです。

https://vuex.vuejs.org/ja/

Vuexは状態管理のためのライブラリではなく、状態管理パターンを実現するためのライブラリというのが大前提にある。 Vuexは状態の更新APIを単方向フローに縛ることでViewを生成する状態の信頼性を担保するFluxアーキテクチャをVueへ持ち込むライブラリ。つまり我々はFluxをVueでやりたいからVuexを使っている。ではFluxを持ち込むと何がよいのか?

Fluxパターンを使うと何が良いのか

まず自分が感じているFluxパターンのメリットはこんな感じ

  • 参照側を全部更新して回る必要がないのでリアクティビティに丸投げして開発ができる(更新し忘れがない)
  • どのコンポーネントから見ても正しい値として担保されているので安心して扱うことができる
  • mutation, acitonでやるべきことが決まっていているので開発時の考え事が減る

Fluxが謳っているsingle source of truthはこれらを成り立たせるのに必要な概念で、単方向フローを確実なものにすることで堅牢性とシンプルさを両立することができる。実際のVue(Nuxt)開発でもこれらの恩恵を受けながら開発できているため、Vuexから始めたことは大きな意義がある。ただ、このままVuexを使い続けるべきかと言われると一考の余地があると自分は思う。

脱Vuexはするべきか

Vuexで出来ないことがそのままサービスの成長を疎外するようなら辞めるべき。メリットを理解して享受できていても、ライブラリは今まさにスケールさせようとしている自分たちのサービスの都合や開発者の期待値を考えて設計されていないため、いつか摩耗する。

ではVuexは現状理想の運用に耐えうるものかどうか、というと確実にそうではないと思う。これはVuexに対して開発者が求めすぎた結果でもあるし、Fluxパターンの規約がそこまで考慮されていない軽量なものということでもある。

Vuexに空いている穴

Vuexは現状以下の点で、Fluxパターンを持ち込むよりも多くのデメリットがあると感じている。

  • stateの直参照ができる&マッピングのAPIが生えている
  • 名前空間の分割に独自のmodule分割(namespace)機構を使う必要がある
  • やることの粒度にかかわらず常に同じ形をしたAPIとフローを定義する必要がある
  • TypeScriptとの相性が良くない

ここで、以下を考えてみて欲しい。

  • Vuexで確実にFluxパターンを守り切ることができるか
    • stateを直接参照することによる潜在的な割れ窓を説明できるか
  • namespaceによる分割が100や200になった姿を想像して、メンテできる自信があるか
  • 何をVuexに任せるのが良いのかを考えた時に、やるべきではないと判断する軸が機械的に決まるか
  • TypeScriptで完全に型付けがされているVuexが動いている場合、何をどこに書けばどういう型のサポートを受けられるかはっきり想像できるか

上記にあげた例は別にVuexが悪いという話ではなくて、今我々はこれらのニーズをVuexに求めてしまっていて、Vuexはそれを考慮していないという事実があるという事。

また、Vuexのnamespaceのようなモジュール分割手法ではそれより小さな粒度で取り回すことが難しく、testを書く場合namespaceの粒度に大きく影響されるという点も辛い。とにかく必要以上に大きな状態を持つということは相応の代償もあるということを再認識する必要がある。大きいということは意図しないものを含んでしまう可能性を常に考慮するべきという話。Vuexのいる場所は、フロントエンドにおいてそれ以上外側がない領域だと考えよう。

余談

Vuexは悪くないとは言いつつも良くないと思っているAPIはいくつかある。stateの直参照に関しては最悪で、Fluxフローを容易に破壊する記述が可能な状態になっている。

computed: {
  message: {
    get () {
      return this.$store.state.obj.message
    },
    set (value) {
      this.$store.state.obj.message = value
    }
  }
}

computed にはgetterとsetterをはやすことができるため、Vuex stateだろうが問答無用でコンポーネント側から書き換えられる。stateの直参照はAPIレベルでできないようにするべき。マーフィーの法則に以下がある。

If there's more than one way to do a job, and one of those ways will result in disaster, then somebody will do it that way.

できるのならいずれ誰かがやる、人類は愚かなので。

自分で実装しないということの言い訳

と書くとちょっと攻撃的になってしまうが、言いたいのはVuexでやりたいことが実現できるからと言ってその副作用を考えずに使い続けるのは難しいということ。状態管理は主語が大きく、グローバルステートによる状態管理とContextやHeadless Instanceによる粒度分割をした状態管理では役割が全く異なるため、Vuexでは大きすぎて不自由になると判断したら自前で状態管理をやっていかなければならない。

Nuxt 2時代、Nuxt 3からはどうか

Nuxt2時代ではVue.observable + Injectでthisに対してDIすることで、コンポーネントより外に状態を露出することなくcontextからリアクティブな値を使うことができた。Vue.observableは単体でもHeadless Instanceとして利用でき、グローバルに露出せずに共有される状態を実現できる。

Vue 3において新しくその立ち位置にくるのはinjectとprovideだが、これの設計についてはまだ誰も規約めいたことをやれていない。つまり再発明する必要がある。Vuexはグローバルにいるということがメリットとなる要件にのみ利用し、その他コンポーネントやページ間のデータの協調をどうするかを考える。OSSになっている必要はなくて、プロダクトの中で積み上げていけば良いと思う。

長くなってきたので一旦〆

参考 https://speakerdeck.com/potato4d/the-last-architecture-of-the-vue-2-dot-x https://techblog.elevenback.co.jp/entry/2019/12/24/020840 https://speakerdeck.com/tooppoo/vuexdehe-wosuruka-he-wosinaika https://qiita.com/tmy/items/a545e44100247c364a71