Vueにはcomputedとwatchという機能があります。どちらも指定したプロパティの変更を検知して、新たな処理を行うものです。
なぜ似たような機能があるの?と疑問に持たれる方がいるいかもしれません。
ここではcomputedとwatchの違いを実例でまとめています。
computedとwatchの違いと使い分け
computedとwatchの違いは使い分け方は以下のようになります。
computedの方が同じ処理を少ない記述で書ける
computedの方が同じ処理を少ない記述で書ける理由
変更を検知してあるプロパティの値を変更すると言った場合、watchを使うよりもcomputedの方が同じ処理を少ない記述で書くことができます。
watchはdataで定義したプロパティを監視対象にするのに対し、computedは監視対象となるプロパティを定義します。
このため、computedの方がwatchよりもより少ないコード量で記述することができます。
実例
例えば、画面上に入力された「名前」と「苗字」から「フルネーム」を作成する処理を作成する場合は以下のようになります。
watchを使った記述
watchを使う場合は、dataで「名前(firstName)」「苗字(lastName)」「フルネーム(fullName)」の3つのプロパティを設定する必要があります。
watchの処理自体も、「名前(firstName)」「苗字(lastName)」のそれぞれの変更を検知する処理を記述する必要があります。
var app = new Vue({
el:"#app",
data:{
firstName:'',
lastName:'',
fullName:''
},
watch:{
firstName(value){
this.fullName = value + " " + this.lastName
},
lastName(value){
this.fullName = this.lastName + " " + this.firstName
}
}
})
computedを使った記述
computedを使う場合は、dataで定義するのは「名前(firstName)」「苗字(lastName)」の2つのみです。
算出プロパティとして「フルネーム(fullName)」を設定します。
また、算出プロパティ「fullName」の中では「名前(firstName)」と「苗字(lastName)」それぞれの変更を検知するので、処理を1行で書くことができます。
var app = new Vue({
el:"#app",
data:{
firstName:'',
lastName:'',
},
computed:{
fullName(){
return this.firstName + " " + this.lastName
}
}
})
上記のようにかなりシンプルに記述することができます。
HTMLのコード
html部分のコードはどちらの場合も同じです。
<body>
<div id="app">
<p>
firstName: <input type="text" v-model:value="firstName">
</p>
<p>
lastName: <input type="text" v-model:value="lastName">
</p>
<p>
fullName: {{ fullName }}
</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</body>
ブラウザの表示
なおブラウザの表示はどちらも以下のようになります。
デフォルトでは「firstName」と「lastName」に何も表示されていません。
↓ 「firstName」に「Steve」と入力
「fullName」の横に入力した内容が表示されます。
↓ 「lastName」に「Jobs」と入力
「fullName」の横に入力した内容が表示されます。
returnせずに変更に合わせて処理を行うなら、watchの方が適している
理由
次は書き方的なところですが、変更内容を検知してプロパティの値を書き変えずに、何らかの処理をするだけであれば、computedよりもwatchの方が、何を監視しているかがわかりやすくなります。
computedでも同じ処理をすることができますが、不要な算出プロパティを定義することになります。
実例
画面上にv-modelで表示されている変数「msg」の変更内容を検知して、コンソールに「変更を検知しました」と表示するプログラムを作成する場合を考えます。
watchの場合
watchを使う場合は、シンプルに監視対象として「msg」を指定します。
var app = new Vue({
el:"#app",
data:{
msg:'初期メッセージ',
},
watch:{
msg(){
console.log("変更を検知しました")
}
}
})
<body>
<div id="app">
<p>
メッセージ: <input type="text" v-model="msg">
</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</body>
監視対象が何か?どんな処理を実行するのかが非常にわかりやすくなります。
computedの場合
computedを使う場合は、新たに別の算出プロパティを用意して、それが発火するように画面の中に埋め込む必要があります。
処理の中にも、変更を検知したいdataプロパティの値を入れる必要があります。
var app = new Vue({
el:"#app",
data:{
msg:'初期メッセージ',
},
computed:{
detected(){
this.msg
console.log("変更を検知しました")
}
}
})
<body>
<div id="app">
<p>
メッセージ: <input type="text" v-model="msg">
</p>
{{ detected }}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
</body>
ブラウザとコンソールの表示
どちらの処理もブラウザの表示は以下のようになります。
デフォルトは「初期メッセージ」と表示されています。
コンソールには何も表示されていません。
↓ 5文字消して「初期」にします。
コンソールには変更を検知した回数だけ、「”変更を検知しました”」が表示されます。
初回ロードで処理を実行したくない場合はwatchを使う
理由
computedを使うと画面の初回ロード時に処理が実行されてしまいます。
変更を検知して初めて、処理を実行したい場合はwatchを使う必要があります。
実例
初期状態は「ここに答えを表示します。」と表示して、質問内容の入力があれば、「入力が完了するまで待機中…」やAPIから取得した回答を表示するプログラムの場合は以下のようになります。
watchの場合
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'ここに答えを表示します。'
},
watch: {
question(newQuestion, oldQuestion) {
this.answer = '入力が完了するまで待機中...'
this.debouncedGetAnswer()
}
},
created() {
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer() {
if (this.question.indexOf('?') === -1) {
this.answer = '質問には?マークを含む必要があります'
return
}
this.answer = '答えを取得中です'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'APIが作動していません:' + error
})
}
}
})
watchでquestionを監視して以下のようにしています。
watch: {
question(newQuestion, oldQuestion) {
this.answer = '入力が完了するまで待機中...'
this.debouncedGetAnswer()
}
},
「answer」プロパティへの値の代入や「debouncedGetAnswer」メソッドの実行を記述しています。
初回はこれらは実行されません。このため画面には以下のように表示されます。
computedの場合
computedの場合は以下のようになります。
var watchExampleVM = new Vue({
el: '#watch-example',
data: {
question: '',
answer: 'ここに答えを表示します。'
},
computed: {
detected(newQuestion, oldQuestion) {
this.question
this.answer = '入力が完了するまで待機中...'
this.debouncedGetAnswer()
}
},
created() {
this.debouncedGetAnswer = _.debounce(this.getAnswer, 500)
},
methods: {
getAnswer() {
if (this.question.indexOf('?') === -1) {
this.answer = '質問には?マークを含む必要があります'
return
}
this.answer = '答えを取得中です'
var vm = this
axios.get('https://yesno.wtf/api')
.then(function (response) {
vm.answer = _.capitalize(response.data.answer)
})
.catch(function (error) {
vm.answer = 'APIが作動していません:' + error
})
}
}
})
questionの変更を検知するため、新たな算出プロパティ(ここではdetected)を用意して、その中に「this.question」を記述しています。
computed: {
detected(newQuestion, oldQuestion) {
this.question
this.answer = '入力が完了するまで待機中...'
this.debouncedGetAnswer()
}
},
また、computedを発火させるために、htmlのどこかに以下の記述を仕込む必要があります。
{{ detected }}
画面表示は以下のようになり、何も入力していないのに、処理が走りAPIと通信が完了した状態になってしまいます。
.htmlの記述
上記のwatchを使った記述と組み合わせるhtmlは以下のようになります。
computedの記述と組み合わせる場合は、「div id=”watch-example”」タグの中のどこかに「{{ detected }}」を記述してください。
<body>
<div id="watch-example">
<p>
質問内容を入力してください:
<input v-model="question">
</p>
<p>「{{ answer }}」</p>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.11"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
</body>
ブラウザの表示
デフォルトでは以下のようになっています。
↓ 「a」を入力します。
「入力が完了するまで待機中…」と表示されたのち、以下のように表示されます。
↓ 「Are you sure?」と入力
「No」と返ります。(タイミングによって「Yes」になることもあります)