影響範囲を考えて@changeとwatchを使い分けよう!
こんにちは!ラブグラフ開発インターンの筒井(@kaito_tsu2i)です!
今回はセレクトボックスを使った実装の際に、Vue.js の@change
とwatch
の使い方に迷ったことがあったので、どうやって使い分けるかの一例を解説したいと思います!
背景
都道府県を選択できるセレクトボックスの値に応じて、市区町村のセレクトボックスの選択肢を絞り込むような機能を Vue.js を使って実装しました。その際に思いついた方法が2つありました。
-
@change
で値が変更されるたびにメソッドを呼ぶ -
watch
で値を監視して変更されるたびに呼ぶ
両方とも同じように見えたので、どっちが適切なやり方かを考えることにしました!
@change
で値が変更されるたびにメソッドを呼ぶ
selectに対して@change
を指定することで、selectのvalueが変更されるたびにonPrefectureIdChanged
メソッドが発火し、選択されたprefectureId
に応じて、その都道府県の市区町村のデータをAPIから取得します。
<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
を発火させることもできます。
<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>
このようにどちらも一見同じような挙動をしているので、特に差は無さそうに感じます。
さて、どちらの方針の方が良いのでしょうか?
もっと先の視点で考える
正直なところ、この機能だけのことを考えると、たしかに挙動が同じなのでどちらのやり方で実装しても構いません。
しかし、プロダクト開発の場合、これからどんどん新たなコードが追加されていきます。
自分の書いたこのコードに自分や他のエンジニアの方が変更・追加を行なっていくという長期的な視野で考えると今回の答えが見えてきそうです…!
影響範囲を考える
ではまず、@change
とwatch
がそれぞれ何の値を見ているのかを見ていきましょう!
-
@change
-
<select>
要素の value を見ている - セレクトボックスで選択された値が変わると、
onPrefectureIdChanged
が発火
-
-
watch
- Vue コンポーネントのプロパティの
prefectureId
を監視している -
prefectureId
の値が変わると、onPrefectureIdChanged
が発火
- Vue コンポーネントのプロパティの
ここからわかるように、@change
が見ているのはあくまでセレクトボックスの value (prefectureId
)のみを見ているので、セレクトボックスで選択した値が変わる時のみonPrefectureIdChanged
は発火します。
一方で、watch
は Vue コンポーネントのプロパティを見ているので、もし仮に将来的に新たなメソッドが追加され、プロパティのprefectureId
に別の値が入る処理が追加されると、今回の意図とは異なる挙動を生んでしまう可能性があります。
今回の意図はあくまで「セレクトボックスで選択された都道府県に応じて、その市区町村を取得する」なので、watch
を使って実装すると、この意図よりも影響範囲が大きくなってしまいます。
このような理由で、今回は@change
を使って実装する方針にしました。@change
を使って実装することで、影響範囲を最小限に抑え、今後コードを追加する際に自分を含めたエンジニアの負担を少しでも減らすことができました。
さいごに
このような一見単純そうな実装でも、もっと広い視野で考えて実装していくことの大切さを痛感しました。自分を含めて、今後新たにコードを追加・変更していくエンジニアが、少しでも快適にコードを書いてもらえるように努力していきたいです!
Discussion