Vue.js入門 Vol.6 ～computedとwatchの使い分け編～

こんにちは！
LIFULLエンジニアの吉永です。
本日も最近あまり関わらなくなったのであまりキャッチアップできていなかったフロントエンド開発技術についてインプットした内容を備忘録として記載していきます。

## 本記事の概要

[Vue.js入門 Vol.1 ～jQueryとの対比編～](https://qiita.com/YoshinagaYuta/items/278a0d6223952ecc7e29)
[Vue.js入門 Vol.2 ～jQueryとの対比編～](https://qiita.com/YoshinagaYuta/items/471fc5cb0cadc8910334)
[Vue.js入門 Vol.3 ～基礎まとめで簡易家計簿を作る編～](https://qiita.com/YoshinagaYuta/items/675e2538fa163f12cd49)
[Vue.js入門 Vol.4 ～外部API呼び出し編～](https://qiita.com/YoshinagaYuta/items/08d78c393319753fe804)
[Vue.js入門 Vol.5 ～業務効率改善ツール作成編～](https://qiita.com/YoshinagaYuta/items/52e86271d52109b2862d)
上記5記事の続きで、Vue.jsのcomputedとwatchの使い分けについて自分なりにまとめてみました。

computedとwatchは似たような機能を持ちつつ、computedで実現できることは基本的にはcomputedを使用するべき、ということが公式サイトやその他解説サイトでも言われています。
となると、具体的にどんな場面でwatchって使ったらいいんだろう？っていうのが気になったので、自分なりに考えた具体例を本記事でまとめてみました。

本記事内で使用しているRGB、HSLの相互変換コードは下記のサイトを参考にさせていただきました。
[RGBとHSLの相互変換[色見本/サンプル付き]](https://www.petitmonte.com/javascript/rgb_hsl_convert.html)

## computedのおさらいと実用例

まずはcomputedを具体的に用いる場面についておさらいします。
computedは算出プロパティなので、DBなどで言うところの導出属性を画面に表示させる際に用いるのが一般的ではないかなと思います。
例えば、税抜き価格をdataプロパティでpriceとして保持している場合に、computedでtaxInpriceなどの算出プロパティを定義して、税込み価格を税抜き価格に税率をかけて算出するというような場面で用いられていると思います。

それでは、computedを`RGBからHSLに変換するツール`で使用した例を見てください。

<p class="codepen" data-height="1700" data-default-tab="js,result" data-slug-hash="ZEKOVRM" data-user="yuta-yoshinaga" style="height: 1700px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/yuta-yoshinaga/pen/ZEKOVRM">
  色コード変換ツール(RGB → HSL)</a> by Yuta Yoshinaga (<a href="https://codepen.io/yuta-yoshinaga">@yuta-yoshinaga</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

上記のツールではRGBのカラーコードからHSLのカラーコードへと変換しておりますが、変換方向が1方向なので、computedでの実装に適しています。

computedは`依存している値が変化することに応じて変化する値`を管理するのに適しているので、`依存しているRGBの数値が変化したらHSLはRGBの値を元に再計算される`ということが非常にシンプルに実現できます。

ただし、このツールの要件が変わって、`RGB、HSL双方向の変換を出来るようにしてほしい`となった場合はどうでしょう？

この場合はそれぞれの値の変化に応じて、それぞれの値が再計算されることになるので、`何かを元に何かを`というcomputedを使うと非常にややこしくなります。
※実現できないことはないが、かなりの力技になるかと

## watchの実用例

このような双方向の変換などの要件が出てきた時は、computedよりもwatchの方が適しているかなと思います。

<p class="codepen" data-height="4200" data-default-tab="js,result" data-slug-hash="jOmrXpZ" data-user="yuta-yoshinaga" style="height: 4200px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/yuta-yoshinaga/pen/jOmrXpZ">
  色コード変換ツール(RGB ←→ HSL)</a> by Yuta Yoshinaga (<a href="https://codepen.io/yuta-yoshinaga">@yuta-yoshinaga</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

上記ツールではまずdataプロパティにRGBとHSLの各値を保持する変数を用意し、それぞれをwatchで監視しています。

watchでは変更に応じてRGB、HSLの相互変換を行っていますが、RGBとHSLの変換ではどうしても誤差が出てしまうので、普通に実装すると値によっては各々の値の変化に応じた再計算による、watchイベントのネストが発生してしまい、入力した値と違う値になってしまうなどの事象が発生してしまいます。

これを防ぐ為に、watchで変更してすぐの変更は一度無視するようなフラグを設け、入力値が意図しない値になってしまわないようにしています。

具体的なフローとしては、まずRGBのR値が変化したことにより、RGBの監視ハンドラが起動します。

このハンドラ内ではRGBの値に応じてHSLを計算し、双方向データバインディングを行っているHSLの変数に格納しています。

すると、HSLの値も変化していることから、HSLの監視ハンドラが起動しますが、RGB監視ハンドラにて変更フラグがtrueになっているので、フラグをfalseにして、何もせずに監視ハンドラの処理を終えるようになっています。

## まとめ

いかがでしたでしょうか？

computedとwatchの使い分けについて自分なりの具体的な活用例をまとめてみました。

基本的にはcomputedで実現できることはcomputedで行うべきという指針がありながらも、時にはwatchを使うことでより柔軟に実装することが可能になることもあるんだということが実感できたのではないかと思います。
※ただ、正直な感想としてはwatchイベントのネストが結構厄介で、相互変換で常に同じ値になる組み合わせだといいのですが、今回のように微妙に誤差が出る組み合わせだと、予想していたよりもちょっと面倒な実装になってしまったなと思います・・・

もちろん今回の例でwatchを使うのがベストプラクティスかと言われると少し自信はありませんし、たとえばv-modelではなく、v-bindとv-onを組み合わせ、methodsで実現することも可能かと思います。

実現する為には色々な手法があり、どの方法が適しているかを短い時間で判別する能力や、どちらの方法の方が処理コストが少ないか、保守性が良いかなどを判別する能力がソフトウェアエンジニアには求められますが、その為にもまずは様々な手法を知っていて、ある程度具体的な実用例も頭の片隅にノウハウとして持っておくことも重要かと思いますので、computedとwatchのように似通っていて、どのように使い分けるべきなのかがチュートリアルや解説サイトだけでは理解しきれない場合には無理やり自分なりの事例を考えて、実装してみるというのはとても良いのではと思いました！

それではまた次の記事でお会いしましょう！