Zenn
🤔

影響範囲を考えて@changeとwatchを使い分けよう!

に公開
1

こんにちは!ラブグラフ開発インターンの筒井(@kaito_tsu2i)です!

今回はセレクトボックスを使った実装の際に、Vue.js の@changewatchの使い方に迷ったことがあったので、どうやって使い分けるかの一例を解説したいと思います!

背景

都道府県を選択できるセレクトボックスの値に応じて、市区町村のセレクトボックスの選択肢を絞り込むような機能を Vue.js を使って実装しました。その際に思いついた方法が2つありました。

  • @changeで値が変更されるたびにメソッドを呼ぶ
  • watchで値を監視して変更されるたびに呼ぶ

両方とも同じように見えたので、どっちが適切なやり方かを考えることにしました!

@changeで値が変更されるたびにメソッドを呼ぶ

selectに対して@changeを指定することで、selectのvalueが変更されるたびにonPrefectureIdChangedメソッドが発火し、選択されたprefectureIdに応じて、その都道府県の市区町村のデータをAPIから取得します。

example_change.vue
<template>
  <select name="prefecture_id" v-model="prefectureId" @change="onPrefectureIdChanged">
    <option value="">選択してください</option>
    <option v-for="prefecture in prefectures" :value="prefecture.id" :key="prefecture.id">
      {{ prefecture.name }}
    </option>
  </select>
</template>

<script>
export default {
  data() {
    return {
      prefectures: [],
      prefectureId: '',
      municipalities: [],
      municipalityId: '',
    };
  },
  methods: {
    fetchPrefectures() {
      // 省略
    },
    fetchMunicipalities() {
      // 省略
    },
    onPrefectureIdChanged() {
      this.municipalityId = ''; // 市区町村をリセット

      if (this.prefectureId === '') {
        this.municipalities = [];
        return;
      }
      this.fetchMunicipalities();
    },
  },
  created() {
    this.fetchPrefectures();
    if (this.prefectureId !== '') {
      this.fetchMunicipalities();
    }
  },
};
</script>

watchで値を監視して変更されるたびに呼ぶ

一方で、v-modelで指定したprefectureIdを watch で監視し、変更されるたびにonPrefectureIdChangedを発火させることもできます。

example_watch.vue
<template>
  <select name="prefecture_id" v-model="prefectureId">
    <option value="">選択してください</option>
    <option v-for="prefecture in prefectures" :value="prefecture.id" :key="prefecture.id">
      {{ prefecture.name }}
    </option>
  </select>
</template>

<script>
export default {
  data() {
    return {
      prefectures: [],
      prefectureId: '',
      municipalities: [],
      municipalityId: '',
    };
  },
  methods: {
    fetchPrefectures() {
      // 省略
    },
    fetchMunicipalities() {
      // 省略
    },
    onPrefectureIdChanged() {
      this.municipalityId = '';

      if (this.prefectureId === '') {
        this.municipalities = [];
        return;
      }
      this.fetchMunicipalities();
    },
  },
  created() {
    this.fetchPrefectures();
    if (this.prefectureId !== '') {
      this.fetchMunicipalities();
    }
  },
  watch: {
    prefectureId() {
      this.onPrefectureIdChanged();
    },
  },
};
</script>

このようにどちらも一見同じような挙動をしているので、特に差は無さそうに感じます。

さて、どちらの方針の方が良いのでしょうか?

もっと先の視点で考える

正直なところ、この機能だけのことを考えると、たしかに挙動が同じなのでどちらのやり方で実装しても構いません。

しかし、プロダクト開発の場合、これからどんどん新たなコードが追加されていきます。
自分の書いたこのコードに自分や他のエンジニアの方が変更・追加を行なっていくという長期的な視野で考えると今回の答えが見えてきそうです…!

影響範囲を考える

ではまず、@changewatchがそれぞれ何の値を見ているのかを見ていきましょう!

  • @change

    • <select>要素の value を見ている
    • セレクトボックスで選択された値が変わると、onPrefectureIdChangedが発火
  • watch

    • Vue コンポーネントのプロパティのprefectureIdを監視している
    • prefectureIdの値が変わると、onPrefectureIdChangedが発火

ここからわかるように、@changeが見ているのはあくまでセレクトボックスの value (prefectureId)のみを見ているので、セレクトボックスで選択した値が変わる時のみonPrefectureIdChangedは発火します。

一方で、watchは Vue コンポーネントのプロパティを見ているので、もし仮に将来的に新たなメソッドが追加され、プロパティのprefectureIdに別の値が入る処理が追加されると、今回の意図とは異なる挙動を生んでしまう可能性があります。

今回の意図はあくまで「セレクトボックスで選択された都道府県に応じて、その市区町村を取得する」なので、watchを使って実装すると、この意図よりも影響範囲が大きくなってしまいます。

このような理由で、今回は@changeを使って実装する方針にしました。@changeを使って実装することで、影響範囲を最小限に抑え、今後コードを追加する際に自分を含めたエンジニアの負担を少しでも減らすことができました。

さいごに

このような一見単純そうな実装でも、もっと広い視野で考えて実装していくことの大切さを痛感しました。自分を含めて、今後新たにコードを追加・変更していくエンジニアが、少しでも快適にコードを書いてもらえるように努力していきたいです!

1
ラブグラフのエンジニアブログ

Discussion

ログインするとコメントできます