虽然计算属性在大多数情况下更合适,但有时也需要一个更通用的方式来响应数据的变化——这就是侦听器(Watcher)。当需要在数据变化时执行异步或开销较大的操作时,侦听器是最有用的。
使用 watch 选项来响应数据的变化:
<div id="app">
<p>
问题: <input v-model="question">
</p>
<p>{{ answer }}</p>
</div>
<script>
new Vue({
el: '#app',
data: {
question: '',
answer: '请输入问题'
},
watch: {
// 当 question 改变时,这个函数就会运行
question: function(newQuestion, oldQuestion) {
this.answer = '等待输入停止...'
this.getAnswer()
}
},
methods: {
getAnswer: _.debounce(function() {
if (this.question.indexOf('?') === -1) {
this.answer = '问题通常需要问号结尾 ;-)'
return
}
this.answer = '思考中...'
var vm = this
// 模拟异步请求
setTimeout(function() {
vm.answer = '这是答案'
}, 1000)
}, 500)
}
})
</script>
// 使用计算属性
computed: {
fullName: function() {
return this.firstName + ' ' + this.lastName
}
}
// 使用侦听器
watch: {
firstName: function(val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function(val) {
this.fullName = this.firstName + ' ' + val
}
}
上面的例子中,计算属性更简洁。但侦听器适合以下场景:
使用侦听器的场景
默认情况下,侦听器在数据改变时才会触发。如果需要在创建时立即执行,使用 immediate:
watch: {
question: {
handler: function(newVal, oldVal) {
this.getAnswer()
},
immediate: true // 创建时立即执行一次
}
}
侦听器默认不侦听对象内部值的变化。要侦听对象内部变化,使用 deep:
new Vue({
data: {
user: {
name: 'John',
age: 20
}
},
watch: {
user: {
handler: function(newVal, oldVal) {
console.log('user changed')
},
deep: true // 深度侦听
}
}
})
性能提示:深度侦听会遍历对象的每个属性,开销较大。如果只需要侦听某个特定属性,使用字符串路径:
watch: {
'user.name': function(newVal, oldVal) {
console.log('name changed:', newVal)
}
}
侦听数组变化:
new Vue({
data: {
items: [1, 2, 3]
},
watch: {
items: {
handler: function(newVal, oldVal) {
console.log('items changed')
},
deep: true // 如果数组元素是对象,需要深度侦听
}
}
})
可以同时侦听多个数据:
watch: {
firstName: function(val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function(val) {
this.fullName = this.firstName + ' ' + val
},
age: function(newVal, oldVal) {
console.log('age changed from', oldVal, 'to', newVal)
}
}
new Vue({
data: {
searchQuery: '',
results: [],
isSearching: false
},
watch: {
searchQuery: _.debounce(function(newVal) {
if (newVal.length < 3) return
this.isSearching = true
this.search(newVal)
}, 300)
},
methods: {
search: function(query) {
var vm = this
fetch('/api/search?q=' + query)
.then(function(res) { return res.json() })
.then(function(data) {
vm.results = data
vm.isSearching = false
})
}
}
})
new Vue({
watch: {
'$route': function(to, from) {
this.loadData(to.params.id)
}
},
methods: {
loadData: function(id) {
// 根据 id 加载数据
}
}
})
new Vue({
data: {
form: {
title: '',
content: ''
}
},
watch: {
form: {
handler: _.debounce(function() {
this.autoSave()
}, 1000),
deep: true
}
},
methods: {
autoSave: function() {
localStorage.setItem('draft', JSON.stringify(this.form))
}
}
})
new Vue({
data: {
theme: localStorage.getItem('theme') || 'light'
},
watch: {
theme: function(newVal) {
localStorage.setItem('theme', newVal)
document.body.className = newVal
}
}
})
new Vue({
data: {
countdown: 0
},
watch: {
countdown: function(val) {
if (val > 0) {
var vm = this
setTimeout(function() {
vm.countdown--
}, 1000)
}
}
}
})
除了在 watch 选项中定义,也可以使用 $watch 方法:
var vm = new Vue({
data: {
a: 1
}
})
// 返回一个取消侦听函数
var unwatch = vm.$watch('a', function(newVal, oldVal) {
console.log('a changed:', oldVal, '->', newVal)
})
// 取消侦听
unwatch()
vm.$watch('someObject', callback, {
deep: true, // 深度侦听
immediate: true // 立即执行
})
// ❌ 错误:箭头函数没有自己的 this
watch: {
question: (newVal, oldVal) => {
this.answer = '...' // this 不是 Vue 实例
}
}
// ✅ 正确:使用普通函数
watch: {
question: function(newVal, oldVal) {
this.answer = '...'
}
}
// ❌ 无法侦听对象内部变化
watch: {
user: function(newVal) {
console.log('changed') // 可能不会触发
}
}
// ✅ 添加 deep 选项
watch: {
user: {
handler: function(newVal) {
console.log('changed')
},
deep: true
}
}
watch: {
question: function(newVal) {
setTimeout(function() {
this.answer = '...' // ❌ this 指向错误
}, 1000)
}
}
// ✅ 保存 this 引用
watch: {
question: function(newVal) {
var vm = this
setTimeout(function() {
vm.answer = '...'
}, 1000)
}
}
// ✅ 或使用箭头函数
watch: {
question: function(newVal) {
var vm = this
setTimeout(() => {
vm.answer = '...'
}, 1000)
}
}
| 特性 | 说明 |
|---|---|
| 基本用法 | watch: { key: function(newVal, oldVal) {} } |
| immediate | 创建时立即执行一次 |
| deep | 深度侦听对象内部变化 |
| 字符串路径 | 侦听嵌套属性 'a.b.c' |
| $watch | 编程式侦听,可取消 |
侦听器是 Vue 响应式系统的重要组成部分,合理使用可以让应用更加灵活。