Vue.js と Redux が出会った
とある現場で Redux にたどり着いた話
- あるところにプロトタイピングを繰り返した結果、1 ファイルにベタッと書かれていたソースがありました。
- なんとかリリースを終えて、運用フェーズに入ったことものの、つらいソースが残されたのです。
- ファイルを再利用可能にするためコンポーネント化を進めることを決意します。
- 最初はとても大きな単位でコンポーネントを作っていたため、大量の状態を 1 コンポーネントが抱えていました。
- コンポーネントの粒度を細かくしていくと、だんだん props のバケツリレーが増えてきます。
- 親から子へ、子から孫へ、孫からひ孫へ…。
- 2way binding してるバケツリレーは、もう追っていくのがツラい!
- ただリレーしてるだけで、中間のコンポーネントでは使ってないものも出てきました。
- 1 箇所直すと、他に影響が出て怖い。
- これは Flux, Redux の出番だ!
なぜ Redux を使うのか
HTML5 とか勉強会の資料を見て、書きたくなった!
前段おしまいです。
- 階層が深くなった時のバケツリレー
- コンポーネント間のデータ同期
この辺りは、みんな同じところで悩みを抱えていて、最近は Redux に流れ着くんだけど、さらなる悩みを抱えてるようですね。
早くその境地にたどり着きたいです。
今回は Vue.js で使ってみるまでの話で止めておきます。
とりあえず資料読む
先に上げた資料もよいのだけれど、はじめての概念でわからないところだらけです。
正直飲み込むまですごく時間かかりました。
Facebook の Flux 実装やら、Redux のドキュメントやら、
いろいろ読んだけど、最終的にブクマしたのは 1 つでした。
これはダイジェスト版なので、実際には元記事の方をじっくり読むといいです。
いろいろ読んでいる過程で実は Vue.js ガイドにも書いてあったのに気づきました。
「状態管理にイラついたら ストアパターン にたどり着くよね。」(超絶意訳)
Vue.js と組み合わせるなら?
なんとなく理解したところで、どの実装を採用するか検討します。
ざっくりわけると公式の実装 or 生 Redux + α
Vuex (採用した方)
pros:
- Vue.js オフィシャルな Redux 実装
- devtools も Vue.js と同じものを使える
cons:
- Redux / Flux と用語違う
- vuex.getters とか vuex.actions とか、 Vue コンポーネントと密すぎる
- vuex-connect というもので疎結合にできそう
- vue-connect だと props とどう住み分けるかでまた悩みそう
Redux + revue
pros:
- 生 Redux 使うので、 Vue と密になり過ぎない
cons:
- 逆に Vue.js と一緒に使うのには面倒
- バインディングとイベントお手玉とか
- middleware 選びで死ぬ可能性
- react-redux のような定番感もない
Vuex サンプル実装
Vuex で簡単な実装をやってみます。main.js
がエントリーポイントで、 App.vue
をマウントします。
.
├── App.vue
├── components
│ ├── CountDisplay.vue
│ └── IncrementButton.vue
├── store
│ └── index.js
└── main.js
store/index.js
import Vue from "vue";
import Vuex from "vuex"; // Vuex モジュールを読み込む
Vue.use(Vuex); // Vue で使えるようにアクティベート
var store = new Vuex.Store({
state: {
// 初期状態を作成
counter: 0,
},
mutations: {
// 状態を変更するメソッドを宣言
INCREMENT(state) {
state.counter++;
},
},
});
export default store;
IncrementButton.vue
<template>
<button @click.prevent="increment">+1</button>
</template>
<script>
import store from "../store"; // 利用するコンポーネントで読み込む
export default {
methods: {
increment() {
store.dispatch("INCREMENT"); // `store.state.counter` を直接操作しない
},
},
};
</script>
CountDisplay.vue
<template>
<span>{{ counter }}</span>
</template>
<script>
import store from "../store"; // 利用するコンポーネントで読み込む
export default {
computed: {
counter() {
return store.state.counter;
},
},
};
</script>
App.vue
<template>
<div id="app">
<increment></increment>
<counter></counter>
</div>
</template>
<script>
import Counter from "./components/CountDisplay.vue";
import Increment from "./components/IncrementButton.vue";
export default {
components: {
Counter,
Increment,
},
};
</script>
Vuex 採用後
既存のソースから、状態と状態の操作だけを取り出して、 Store
にまとめるの大変でした。
フロントエンドにおける Model って、なんなんだろうという闇に囚われたことも…
サンプルでやったように store.dispatch()
をコンポーネントから呼ぶことはないかもしれないです。
今は、状態操作をまとめた action.js
を作って、API 通信やデータフォーマットしてから store.dispatch()
しています。
つまりは Action Creators がいる状態に落ち着きました。
実は、Store を EventEmitter で自力実装も簡単にできるレベルです。
Vuex を採用しても、お別れすることもすぐにできる。
大事なのは Redux の概念の方だった。
よく見かけるこの図が飲み込めるようになるまで、時間がかかりました。
採用から数カ月後
自分以外にも関わる人が増えたり、別のプロジェクトでも啓蒙するようになりました。
入門レベルだと、ファイルが分割されて、どこからどう処理が流れるのか迷子になりがちです。
まずは、 state と mutation をまとめること。
それから、 action は作らないでコンポーネントから store.dispatch()
するものを作ります。
その後は、規模に応じて action をいれていくという段階を踏むのが、理解には必要な過程みたいです。