コード
# CoffeeScriptで御免
Vue.directive 'change',
acceptStatement: true
priority: 1024
update: (fn)->
model = @el.getAttribute Vue.config.prefix + 'model'
@handler = ((v)->
cache = @vm.$value
if model
@vm.$value = v
else
@vm.$value = @el.value
fn @vm.$value
@vm.$value = cache
).bind this
if model
@vm.$watch model, @handler
else
@el.addEventListener 'input', @handler
unbind: ->
@el.removeEventListener 'input', @handler
ちゃんと使うならタグの制限やtype
属性のチェックが入るけど、最低限の骨組みはこんな感じ。
使い方
new Vue
template: ...
methods:
func: (v)->
console.log v
なVMで
<input type="text" v-model="hoge" v-change="func(hoge)">
とか
<input type="text" v-change="func($value)">
とか。$value
がv-on
の$event
みたいに使える。
解説
v-modelとの併用
v-model
が与えられている場合は$watch
を使うようにしている。input
イベントはモデルの値が更新される前に発生するので、v-model
がある場合でもinput
イベントからコールバックを実行してしまうと前回の値が使われてしまうからである。またコールバックに渡す値はnumber
属性を一緒に使うときなど、@el.value
が必ずしもv-model
でバインドされた値と一致しない場合があるので、v-model
がある場合は$watch
で呼び出された引数から引っ張っている。
priorityについて
priority
はディレクティブ評価の優先度を設定する。高い方が先に評価される。v-modelのpriority
は800
で、デフォルトは0
である。ドキュメントのどこにも書いてないけど、priority
は
v-modelはこことデフォルトはここで確認できる。
Vue.jsのディレクティブではAngularとちがって評価後は要素から属性が取り除かれてしまうため、v-model
を読み取ろうと思ったらpriority > 800
にする必要がある。
$eventみたいなローカルスコープっぽいやつ($value)の作り方
行儀悪いように見えるけど、単純にv-on
のソースでやってることの流用である。$event
は特殊なことをしてると思っていたのだが、このやり方で実現してるのを確認したときは少々面食らった。ただ、もともと内側からは$
で始まるモデルが作れないの対し外部からは普通に書き込みできるので、一瞬だけ埋め込んで外すというやり方は全てが同期的に行われる限り合理的ではある。
動機など
基本的にVue.jsではインタラクティブな要素に直接ハンドラを与えて変更を拾うようなディレクティブは存在しないので、vm.$watch
やComputed propertyのsetterを用いるしかない。
変更の通知のためだけにモデルを用意してvm.$watch
をVMの実装から呼ぶなんて到底まとな人間の所業ではないし、Computed propertyにしてもv-model
で使うにはgetterが必要で、状態の保存のためは結局モデルを用意しないといけないし、しかもそのモデルの名前はComputed propertyとは別にしないといけないので、命名で気を揉んで辛いってのが何よりの動機。
あとは「methods
に直接変更のハンドラを書ける必要がある」という意識もある。
Vue.jsの開発周りの議論などを詳しく追っかけてないんで、なんで実装されてないのかわからないが、何か深いわけがあってv-change
をビルトインで持っていないのであれば理由を知りたいところである。