なぜVuexじゃないのか
先日来、ViewModel フレームワークが片っぱしから値の変化やイベントを検出して View を書き換えようとする姿勢に疑問が生じていて、本来は MVVM とかじゃなかったの? M 無視しすぎじゃね? あるいは M は全部 HTTP とか割り切りすぎじゃね? という感じになってきていた。1
それで検出されないようにするにはどうしたらよいかというのが昨日の「ViewModel系フレームワークでデータの変化を監視させない - あーありがち(2018-05-01)」になるわけだけど、これはいわゆる VM component 内の話。でも実際のところ Model はだいたいグローバルでシングルトンになるんじゃねーの?という気がしていて、そうなると component でだけ実現できてもなーと思うわけですよ。
で、じゃあグローバルでシングルトンてことになると Flux 系 Store 具体的に例えば Vue.js だとまぁ Vuex になるんだと思うけど、その Vuex ではこういうことはできない。Vuex は雑に言うと監視対象となる state とその state に変更を加えることを許された mutation から成る2 が、これらはそれぞれ独立した function であり、mutation には基本的には state しか渡ってこない。mutation 以外に action や getter も定義できるが、事情は同じである。すべて state を中心に置いている。そして改めてくり返すが、その state はすべて監視対象というわけだ。
class ベースの React.Component みたいに Store という名の Model 相当の class があって自由に property を増やせるわけではない。その強い制約こそがグローバルな状態管理によいのかもしれないが、それにしてもなぁという感じ。
MobXのサンプルコードを見てみる
そこで MobX.
こいつは Store というよりは基本的には Observer と言ってよいと思う。
公式のサンプルコードを引用するとこんな感じ。
import { observable } from "mobx"
class Todo {
id = Math.random();
@observable title = "";
@observable finished = false;
}
ここで「これ JS か?」と思うかもしれないけど、class property と decorator を Babel ってやれば使えるれっきとした ES.next valid な JS なので、JS 脳が化石化してる人は慌てた方がよいかもしれない。
ポイントは @observable で、この decorator がついている値だけが監視対象になる。ね、これこれ、こういうことですよ、やりたいのは。
ついでにこの observable な値に変更を加えるには @action を付けた function を経由するとデバッグしやすくてよいよ、ということなので、それらを適当に書き足すと
class Todo {
@observable title = "";
@action setTitle(title) {
this.title = title;
}
}
こんな感じ。
これは「各自が気ままに値を書き換えると追えなくなるよ」という教えに従っているだけで、絶対守らなきゃいけないってわけじゃないけど、まぁ守っておこう。別に混乱を生みたいわけじゃない。
実際にVue.jsで試してみた
Vue.js と MobX をブリッジしてくれるライブラリは有名どころで二つある。これ以外にもあるのかもしれないけど、見つけられなかった。
実際に利用したパッケージは
- mobx 4.2.0
- vue-mobx 2.0.0
- movue 0.3.0
ここで雑に比較すると、とにかく component 内での記述を少なくしたいなら vue-mobx がオススメ。Vue.use の記述は済んでいるものとして component 内のコードはこれだけで済む。
import Model from '..'
export default {
fromMobx: {
Model
}
}
なんと、fromMobx に Model を与えるだけ。こうしておいて、この object を食わせて Vue component を生成するだけでいきなり Model 内の observable な値と action が component 内に生える。
いきなり生えるのが Ruby の module っぽくて「おぉ、なるほど」となるのだが、さすがにどんな component から呼ばれるか分からない Model の observable と action がそのまま生えると、やたらと VM component が増えがちな昨今のフレームワークでカジュアルに使うにはちょっとまずそうな気がしないでもない。
そんなあなたには movue.
movue も fromMobx という property で VM component 内に Model の情報を呼んでくるのは一緒だけど、この fromMobx では細かく設定をコントロールできるので、こっちの component ではこの名前で、あっちの component ではこの名前で、といった使い方ができそう。
Decorator対応
MobX の decorator は optional なので、実はさっきの例は以下のようにも書ける。
import { observable, decorate } from "mobx"
class Todo {
id = Math.random();
title = "";
finished = false;
}
decorate(Todo, {
title: observable,
finished: observable
})
こっちの方がより広範囲で動くのだけど、確かに decorator を使えた方が記述が分離せずに分かりやすい。
ということで使えるようにしていこう。
Nuxt x Babel対応
正直ややこしい。
Babel は 6 と 7 の間で decorator の扱いが変わっている。今回の実験は Nuxt.js 上で行っており、2018-05 現時点で Nuxt 1.0.0 は Babel 6 依存なのでそれで動くようにしていく。
babel-preset-mobx があるのでそれを使ってみたのだが、これは動かなかった。
zwhitchcox/babel-preset-mobx: All dependencies mobx depends on
中身を見ると preset と言いつつ実際には以下の4つの plugin を利用する
- babel-plugin-transform-class-properties
- babel-plugin-transform-decorators-legacy
- babel-plugin-transform-es2015-classes
- babel-plugin-transform-regenerator
Nuxt.js の Babel の設定は vue-app ( vue + babel-preset-env ) なので本当に必要なのは以下の2つ
- transform-decorators-legacy
- transform-class-properties
これを plugins に追加してやれば ok
順番が大事。 decorators-legacy の次に class-properties ここら辺は Babel のイヤンなところ。エラーを見ても何が起きてるのか分からん。
おまけ VS Code 対応
decorator でずっと警告が出るので ESLint の設定がちゃんと反映されてないのかなーと思って疑ってたけど、これは VS Code で独自に出してるやつでした。
javascript.implicitProjectConfig.experimentalDecorators": false,
を true にすると解決。
まとめ
MobX は Redux の面倒くささを解消してくれるとか jQuery とも合わせられるとかいろいろ言われている最近注目の状態管理ライブラリらしいけど、「observable で明示するだけ」というシンプルさは自分の考えている、もっと広く一般的な Model としても扱いやすそうに見えた。
昨今の ViewModel フレームワークが薄いフレームワークになることで View 側の問題の本質に注力しているように、Model 側は Model 側で「Backbone.Model のようないい具合にイベント起こしてくれる程度のシンプルなやつねーかな」と思っていたところに MobX を見つけることができたので、自分の中では割と期待が大きくなってきている。
(これなら既存の jQuery を叩き直すのにも、何かの拍子に付き合わなきゃいけなくなる可能性の高い React ともなんとかやっていけるかも?)
まだテスト周りがよく分かっていないので、次はテスト周りをチェックして、よさそうなら今後は MobX に振っていくかもしれないなーという気がしてます。