JavaScript
vue.js
Vuex

Vue.js + Vuexでデータが循環する全体像を図解してみた

表参道のプログラミングスクールでVue.js(+Nuxt.js)を教えています。
とある生徒に「Vue.jsとVuexの関連がわからないので図解して欲しい」と頼まれてホワイトボードにサクッと書いて説明してみたらわかりやすいと大好評。
しかもその生徒が私のラフ図をキレイな動画に作り直してくれたのでカットごとの紹介記事を書くことにしました。

0.jpg

この記事でわかること

state/mutations/commit/actions/dispatchの違いがわかるようになります。
getterやsubscribeについては扱いません。
実際のコードの書き方にも触れません。
Vuexを使ってはいるけど理解できなくて苦しんでいる方向けの内容です。

なぜVuexを使うのか

Vuexの図解に入る前に、私がVuexを使っている理由を説明しておきます。
Vuexのメリットが明確でないと図があっても理解が進まないですからね。

Vue.jsに限らず、コンポーネント指向のMVCライブラリは中~大規模開発を想定しています。
ちょっとしたサイトに動きを与えるぐらいなら今でもjQueryが圧倒的に便利です。
大規模開発になるとコンポーネントの数は100個を超えることも珍しくありません。

ルートコンポーネント>子コンポーネント>孫コンポーネント>ひ孫コンポーネント>...
と続いていくと、今自分が開発しているコンポーネントが何層目にいるのか全くわからなくなります。
この現象はまるで映画「インセプション」の世界。夢の中の夢に入り続けるうちにどこが現実かわからなくなるあれです。

インセプションなVue.jsの世界で主要なデータを標準機能のpropsと$emitでやりとりすることを想像してください。
1.jpg

このくらいシンプルな構造なら問題ないですが先祖から子孫へ、何世代にも渡ってデータを受け渡し、子孫から先祖へ何世代も遡ってイベントを通知することを考えたら地獄絵図です。

2.jpg

そんな「データとイベントのバケツリレー地獄」から脱するためにVuexを使っています。
「自分が何層目にいるのか?」を把握しながらコーディングしようとするのは無意味です。
映画ではディカプリオがコマ?を回して現実にいるかどうかを確かめていましたが、我々は何層目にいようがどうでも良い。

逆に言うと、コンポーネントが少ない場合は恩恵がないかもしれません。
本やサイトのVuexサンプルを叩いてもメリットがわからない原因は多分そのアプリが小規模だからです。
この記事を読み終わったらぜひ、Vuexのパワーを発揮できるスケールのウェブアプリを開発してみてください。

Vuex図解

①アプリ1つに対してStoreも1つ

3.jpg

ページではなくアプリ単位で考えます。
vue-cliやNuxt.jsを使っている場合はディレクトリ全体で1つのアプリという構成になっているはずです。

②データの変更は必ずmutationを経由

4.jpg

mutationsに登録した関数を呼ぶために使うのが「$store.commit()」です。
storeのmutationsに登録した関数以外の場所でstateを変更してはいけません。

6.jpg

正確な例えではありませんが、$store.commitは$emitに似たような役割を担っています。
子が$emitしたら親コンポーネント側で呼んでいたmethodsに相当するのがmutationsのようなイメージです。

③storeに保存したデータはstateから読み取る

7.jpg

あらゆるコンポーネントが$storeにアクセスできます。
データの読み取りは$store.stateで。
②で説明した通り、読み取ったstateを直接書き換えてはいけません。

④mutationは同期的にstateを書き換える

8.jpg

つまり、$store.commit()を呼んだ次の行でstateは期待通りに変更されています。
mutations内でajax通信のあとにstateを書き換えるような処理をしてはいけません。

⑤非同期にstateを書き換えたいならactionを使う

ajaxやsetTimeoutやFirebaseのような非同期処理の後にstateを書き換えたい場合にはactionsに非同期の関数を登録します。

9.jpg

actionを呼び出すときには$store.dispatchを使います。

10.jpg

非同期処理が終わっても、actionの関数内でstateを書き換えてはいけません。
必ず、$store.commitを呼びmutationを経由させます。
当然1つのactionのためだけにmutationを1つ余計に登録することもあります。

11.jpg

非同期処理を扱うので、actionはasync/awaitを使うと便利です。
Promiseではない非同期関数を扱うときは通常のcallbackで書きます。

12.jpg

⑤Vuexはデータの循環経路を整備してくれる

13.jpg

props/$emitはデータがコンポーネントの層を上下に移動するイメージでした。
Vuexを使うと他のコンポーネントとの入れ子構造を気にすることなくシンプルな経路でデータを読み書きできるようになります。

いつdata/props/$emitを使うのか

Vuexがあってもdata/props/$emitを使う場面はたくさんあります。

  • そのコンポーネントでしか使わないデータ
  • セレクトボックスやモーダルウィンドウの開閉のような揮発性の情報
  • 親子間2層だけで情報を受け渡す
  • 単機能のコンポーネントを使う、作る

上記の場合でも何らかの方法(localStorageやFirebase)で該当のデータを永続化する必要があるならVuexを使いたいですね。

慣れると非常に便利な機能なんですが、常に全てのデータをVuexで管理すれば良いというわけではありません。
あらゆる機能には適材適所があり、アプリの規模、チームの技術力、コンポーネントの数によってもVuexを使うかどうか判断すべきです。

動画を作ってくれた人

私のVue.js講座の聴講生です。江戸川区でフロントエンジニアをやっているようです。
Qiitaに動画を埋め込めないのでYouTubeとかにアップされたら紹介します。

アイコン画像
@shiba_mitue

Travis-CIでsass+compassでCSS生成を自動化
Firebase Hosting+Travis-CIでGitHubからデプロイを自動化
ピュアCSSでボタンアニメーション+jQueryでアニメーション終了イベントを取得する
javaScriptでWEBカメラの映像をブラウザで表示する(PC/iPhone)

個人的な感想

3年ぶりにQiita投稿しました。
図解はわかりやすくて良いですね。
ネタはまだまだあるので定期的に投稿できるように頑張ります。