メドピア開発者ブログ

集合知により医療を再発明しようと邁進しているヘルステックリーディングカンパニーのエンジニアブログです。PHPからRubyへ絶賛移行中!継続的にアウトプットを出し続けられるようにみんなでがんばりまっす!

v-onから辿るVueの細道

みなさんこんにちは、フロントエンドピラティストの小宮山です。
しばらく休養していたランニングを再開し、ハムストリングスの探求に勤しんでいるのが近況です。

v-onの不思議

templateでのv-onの書き方にはいくつかバリエーションがあります。
なんとなく書いてもVueがいい感じに解釈してくれてしまうので普段はあまり気にしていないんですが、よくよく考えてみると不思議な挙動をしているようにもみえてきます。

最もオーソドックスなのはこれではないでしょうか。

<input type="text" :value="value" @input="input" />

methodsとして定義しておいた関数をそのままイベントハンドラとしてtemplateに埋め込む形です。

methods: {
  input() { ... }
}

こう書いても動作は同じです。

<input type="text" :value="value" @input="input()" />

見慣れたtemplate構文だと思いますが、@input="~~"~~に書かれた処理はJavaScriptの構文としては全くの別物です。一方は関数の参照であり、もう一方は関数を実行した戻り値であるはずです。
なのにVueのイベントハンドラとしては両者の動作は同じです。

なんででしょう?不思議に思いますよね、思ってください。思ってくれたことにしてこのまま話を続けていきます。

v-onの書き方バリエーション

まずはv-onの書き方を種類分けしていきます。これは特に公式にそういう区分があるわけではなく、勝手に分類してみただけです。

methods埋込み型

<input type="text" :value="value" @input="input" />

オーソドックスな書き方。

methods実行型

<input type="text" :value="value" @input="input('hoge')" />

引数を指定したいときに使う書き方。

③ 関数埋め込み型

<input type="text" :value="value" @input="(val) => $emit('input', val)" />

親コンポーネントにイベントを渡して行きたいときによく使う書き方。

④ 式埋め込み型

<input type="text" :value="value" @input="value = value + 'a'" />

methodsにするのが面倒なときに使う書き方。

おそらくこの4種類が代表的な書き方ではないでしょうか。
例示のために微妙に処理内容を変えてしまいましたが、どの書き方をしてもイベントハンドラとしては期待通りの動作をしてくれるのはみなさん御存知の通りです。

v-onの書き方によるパフォーマンスの違い

4種類それぞれがパフォーマンスに与える影響が気になるところです。

実は先日社内フロントエンド勉強会の場でReact入門が実施され、hook周りの仕組み、特にuseCallbackによるイベントハンドラ最適化の努力にとても興味を惹かれました。useCallbackを使わない場合と比べてコードが冗長になるのは間違いないのに、それを受け入れてまで最適化に不断の努力を行うReactの姿勢には鬼気迫るものがあります。

Reactがここまでやっているんだから、じゃあVueはどうなのよというのは当然の疑問です。実はこの疑問から始まってv-on周りの挙動やコードを調べて回った結果がこの記事だったりします。

本題に戻り、v-onの書き方によるパフォーマンスを検証していきたいと思います。

パフォーマンスの差はありません。

結論がでました、この記事の本題は以上です。
すみません終わりません。ちゃんと根拠を提示する義務を果たします。

v-onのコードを読む

実は当初はパフォーマンスの差があるだろうと決めつけ、ブラウザの開発者ツールでメモリ利用量とにらめっこしたりしていました。
ただどう頑張っても、有意に差があるだろうと見て取れるような状況は発生していませんでした。

そしてv-onの書き方ごとのパフォーマンスグラフを貼り付けて、「こんなにパフォーマンスに差が出る!」「こういう書き方をするVue使いは素人」「これからはこの書き方一択」というマウンティングをかましていくという目論見は見事に崩れ去りました。

f:id:robokomy:20190912140827p:plain
変わり映えしない!

マウンティングは失敗でしたが、なぜ差がでないのかという新たな疑問を抱いてしまうのがエンジニアの性です。差がないと性がかかってしまったなと気にし始めるのもきっとエンジニアの性です。

パフォーマンス差がでない理由をパフォーマンス計測から見つけるのは難しいので、Vueの実装を探索していきます。

以下ではv2.6.10タグのコードベースで紹介していきます。

github.com

はい、見るべきコードはここです。

これが何かというと、@input="~~"~~に書かれた文字列をイベントハンドラとして実行できる関数に変換している部分の実装です。

まずはこの分岐に、先に上げたv-on記法の①と③が突入します。細かい説明は省きますが、handler.valueに上述の~~に書かれた文字列がそのまま入っています。
そのままreturnしているので、イベントハンドラとして~~に書かれた関数がそのまま実行されます。①と③は関数の参照なのでそのまま実行するだけというわけです。

ちなみに$emitしたときはこんな感じでイベントハンドラを実行しています。使う側には魔法に見えても、Vueの内部実装はもちろん魔法ではなく地続きの実装です。

さらに脇道にそれると、thisを付けなくてもtemplate内でmethodsを使えたりするのはここでwith(this)とされているからでした。改めて実装を探ってみると、なるほどなぁという発見がたくさんあります。

(へー便利そうと思っても軽い気持ちでアプリコードにwithを使うのは絶対にやめましょう。)

再び本道に戻ります。

残りの②と④はこちらの分岐で、②だとisFunctionInvocationtrueになり、④だとfalseです。returnされるかという違いはありますが、function($event){ .. }でラップすることで、どちらも~~に書かれた処理がそのまま実行されるのが特徴です。

function($event){ .. }というラップを利用し、イベント引数を$eventという変数として受け取るなんて小技もあったりします。実は今回始めて知りましたが、ちゃんとドキュメントのこのあたりにも書いてあります。

ドキュメントだとネイティブのDOMイベント用っぽいですが、非ネイティブなコンポーネントについても同じイベントハンドラの文字列パースがされているので共通で使うことができます。
イベントをそのまま親コンポーネントに渡したいときのショートハンドとして使えなくもないかもしれません。

<input type="text" :value="value" @input="$emit('input', $event)" />

ただし受け取れるのは第一引数のみです。複数の引数が欲しい場合は素直に(a, b) => hoge(a, b)という形にするか、argumentsを使う必要があります。

@input="hoge(...arguments)"という書き方をするとbabel変換とVueコンパイラの相性が悪いのかエラーになってしまうんですが、この書き方ができる詳しい条件ご存じの方いたら教えて下さい。

v-onの書き方によるパフォーマンス結論

Vueの実装をざっと眺めてもうお気づきだと思いますが、今回紹介した4種類のv-on記法はいずれも同じような加工とパース処理を経て実際に実行されるイベントハンドラへと変換されます。
加工とパースに若干の差異はあるものの、その後の処理に差はありません。

そして加工とパース処理は基本的にビルド時に行ってしまいますので、ランタイムにおいてパフォーマンスに差がでることもないというわけです。function($event){ .. }によるラップで関数呼び出しのネストが増えるのは確かです。とはいえさすがにその差を気にする必要に迫られる環境は皆無だと思います。

今回はv-onについての調査でしたが、render関数を作って直接onの設定をした場合は状況が別物です。この場合はrenderが評価される度にそこに書かれた処理が実行されるので、ReactがuseCallbackで最適化を目指したのと同じような状況が生まれます。

Vueのtemplateを使っている限りv-onのパフォーマンスを気にする必要はない、というよりしても何もできないが正しいですが、renderを使う場面に遭遇したらパフォーマンスについても気にしておくことをおすすめします。

まとめ

v-onを使うときに気にするべきはパフォーマンスではなく、可読性。

もう締めに入っているのに唐突に可読性という主張を始めてしまいました。
templateに複雑な処理を書かないようmethodsに切り出していくことはもちろん重要です。 重要ですが、その理由はパフォーマンス観点から来るものではないというのはこれまで見てきた通りです。

では何の観点かというと、やはり可読性ではないでしょうか。
ただフラグをトグルだけの処理や、ただ親に$emitするだけの処理までmethodsに切り出すべきか判断に迷ったときは是非ともパフォーマンスではなく可読性に重きを置いていきましょう。


(☝︎ ՞ਊ ՞)☝︎是非読者になってください


メドピアでは一緒に働く仲間を募集しています。 ご応募をお待ちしております!

■募集ポジションはこちら

https://medpeer.co.jp/recruit/entry/

■開発環境はこちら

https://medpeer.co.jp/recruit/workplace/development.html