58
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Vue3】もはやVuexとかPiniaって使わなくて良くない?

Posted at

Vueの状態管理にはVuexやPiniaを使いましょう、と言われるが外部のライブラリ使わなくてもVue3本体の機能だけで十分では?むしろそっちの方がメリット多くない?という話。

状態管理ライブラリ使わずにどうやってストア(状態とアクション)を定義するの?

Vue3から導入されたComposition APIを使えばできます。例えば

~/composables/todo.js
import { reactive, computed, readonly } from 'vue';


export function useTodo() {
  const items = reactive([]);

  const firstItem = computed(() => items[0] ?? null);

  function add(todo) {
    items.push(todo);
  }

  return { items: readonly(items), firstItem, add };
}

Composition APIというVue本体の機能だけで簡単に状態とアクションが定義できました。reactive, ref, computed, watch, readonlyなどの関数を駆使することで状態管理ライブラリでできることはおよそ全てできるどころか、Composition APIの方がより柔軟かつシンプルに書けている思います。

どうやってストアをコンポーネントを跨いでグローバルに共有できるようにするの?

Vue本体の機能であるprovideinjectを使えばできます。provide関数とinject関数はVueの依存関係注入の仕組みで、親のコンポーネントでprovide('key', value)で定義した値をその子コンポーネントからconst value = inject('key')とすることで取り出すことができます。

なのでアプリ全体で共有するストアはトップのAppコンポーネント内で生成してprovideすれば、そのストアを使いたい各コンポーネントはinjectで使うことができます。

~/App.vue
<template>
<router-view />
</template>

<script>
import { provide } from 'vue';
import { useTodo } from '~/composables/todo.js';


export default {
  setup() {
    const todoStore = useTodo();
    provide('todo', todoStore);
  },
};
</script>
~/pages/list.vue
<template>
<ul>
  <li v-for="item of todos">{{ item }}</li>
</ul>
</template>

<script>
import { inject } from 'vue';


export default {
  setup() {
    const todo = inject('todo');
    return { todos: todo.items };
  },
};
</script>

この仕組みを使えばグローバルに共有するだけでなく、特定のコンポーネントツリーの中だけで共有するストアも作れます。規模の大きなアプリで、ある機能群の中だけでストアを共有するとかできるかもしれません。VuexやPiniaではこのようなことはできません。

あるストア内から別のストアを参照したいときどうするの?

ストアを生成するときに参照したいストアを引数で渡します。例えば以下のuseTodoSearch関数のようにします。

~/pages/list.vue
<template>
<input v-model="searchQuery" />
<ul>
  <li v-for="item of todos">{{ item }}</li>
</ul>
</template>

<script>
import { inject } from 'vue';
import { useTodoSearch } from '~/composables/todo-search.js';


export default {
  setup() {
    const todo = inject('todo');
    const todoSearch = useTodoSearch(todo);
    return { searchQuery: todoSearch.query, todos: todoSearch.matched };
  },
};
</script>

useTodoSearchの実装は以下のようになります。

~/composables/todo-search.js
import { ref, computed } from 'vue';


export function useTodoSearch(todoStore) {
  const query = ref('');

  function matches(q, todo) {
    // todoが検索クエリqにマッチするかどうかを返す関数
  }

  const matched = computed(() => todoStore.items.filter(
    item => matches(query.value, item)
  ));

  return { query, matched };
}

これによってストア間の依存関係が明確になるというメリットがあります。VuexやPiniaではストアはグローバルにどこからでも参照できるのでストア間の関係性は不明です。VuexやPiniaのストアは本質的にはあの忌避すべきグローバル変数であり、あらゆる場所から状態が変更されうるし、あらゆる場所でその状態に依存した処理が書けてしまいます。

状態管理ライブラリの利点

VuexやPiniaなど個別の状態管理ライブラリを使う利点としては、高機能なデバッグツールが挙げられます。ブラウザの拡張機能を入れることで各ストアの現在の状態を一発で確認したり、過去の状態に遡ったりもできたりします。ここで解説したVue3のみを使った方法ではそのようなことはできません。

また、私は詳しくわかってないですが、SSR(Server-Side Rendering)等への対応が最初からなされていて自分で色々やる必要がないというメリットもあるのかもしれません。

あとがき

Composition APIは非常にスマートにストアを定義できていると思います。VuexやPiniaはVue2のOptions APIチックで論理的な機能の分離ができず読みづらいです。また外側から見たときに、状態を根源的な一次状態stateとそこから派生した二次状態getterに分離したり、アクションも同じようにmutationactionに分けたりしていますが、これらの機能もストアの使い方を無意味に複雑化しているだけ感があり、好きではありません(特にVuex)。(PiniaはComposition APIでも定義できる模様、公式ドキュメントでほとんど解説ないので謎だけど)

Vue.js devtoolsがもっと進化してVue本体だけでもデバッグがしやすくなるとベストなんですけどね。それが難しいから状態管理ライブラリは変な定義方法を要求するのかねえ。

58
41
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
58
41

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?