Vue3 监听属性(Watchers)语法知识点及案例代码
在 Vue 3 中,监听属性(Watchers) 允许你响应数据的变化,并在数据变化时执行特定的逻辑。监听器对于处理异步操作或需要在数据变化时执行复杂逻辑的场景非常有用。
一、监听属性的基本语法
1. 使用 watch
选项
在组件选项中,通过 watch
选项来定义监听器。watch
对象中的每个键对应一个要监听的数据源,值是一个回调函数,当数据源变化时回调函数会被调用。
export default {
data() {
return {
count: 0
};
},
watch: {
count(newVal, oldVal) {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
}
}
};
2. 使用 watch
函数
除了在选项式 API 中使用 watch
选项外,Vue 3 还提供了组合式 API 中的 watch
函数,用于在 setup
函数中定义监听器。
import { ref, watch } from 'vue';
export default {
setup() {
const count = ref(0);
watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
});
return {
count
};
}
};
3. 监听器的选项
watch
回调函数可以接受多个参数:
- 新值(newVal):变化后的新值。
- 旧值(oldVal):变化前的旧值。
- onInvalidate:用于清除副作用的函数。
此外,监听器还支持一些选项:
- immediate:布尔值,设置为
true
时,监听器会在初始化时立即执行一次。 - deep:布尔值,设置为
true
时,监听器会进行深度监听,适用于监听对象或数组内部的变化。
watch(
source,
(newVal, oldVal) => {
// 回调函数
},
{
immediate: true,
deep: true
}
);
4. 监听多个数据源
可以通过数组的形式同时监听多个数据源。
watch([() => this.a, () => this.b], ([newA, newB], [oldA, oldB]) => {
console.log(`a 从 ${oldA} 变为 ${newA}, b 从 ${oldB} 变为 ${newB}`);
});
5. 停止监听器
在某些情况下,你可能需要手动停止监听器。可以使用 watch
返回的函数来停止监听。
const unwatch = watch(count, (newVal, oldVal) => {
console.log(`count 从 ${oldVal} 变为 ${newVal}`);
});
// 需要停止监听时调用
unwatch();
二、案例代码
下面是一个完整的 Vue 3 组件示例,演示了如何使用监听属性。这个组件包含一个计数器,用户可以点击按钮增加计数,监听器会监测计数的变化并执行相应的逻辑。
1. 使用选项式 API 的示例
<template>
<div>
<h2>计数器</h2>
<p>当前计数: {{ count }}</p>
<button @click="increment">增加</button>
<p>监听器输出:</p>
<ul>
<li v-for="(log, index) in logs" :key="index">{{ log }}</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
logs: []
};
},
watch: {
// 监听 count 的变化
count(newVal, oldVal) {
this.logs.push(`count 从 ${oldVal} 变为 ${newVal}`);
// 示例:每当 count 变化时,执行异步操作
this.fetchData(newVal);
}
},
methods: {
increment() {
this.count++;
},
async fetchData(val) {
// 模拟异步请求
const data = await new Promise((resolve) => {
setTimeout(() => {
resolve(`服务器返回的数据: ${val}`);
}, 1000);
});
this.logs.push(data);
}
},
// 立即执行一次监听器
mounted() {
this.$watch(
() => this.count,
(newVal, oldVal) => {
this.logs.push(`[立即监听] count 从 ${oldVal} 变为 ${newVal}`);
},
{ immediate: true }
);
}
};
</script>
<style scoped>
button {
margin-top: 10px;
}
ul {
list-style-type: none;
padding: 0;
}
li {
background: #f0f0f0;
margin: 5px 0;
padding: 5px;
}
</style>
解释:
数据定义:
count
:当前的计数。logs
:用于存储监听器输出的日志。
监听器:
watch
对象中定义了count
的监听器。当count
变化时,回调函数会执行,将新旧值记录到logs
中,并调用fetchData
方法模拟异步操作。
方法:
increment
:增加count
的值。fetchData
:模拟一个异步请求,延迟 1 秒后返回服务器数据。
生命周期钩子:
mounted
:在组件挂载后,使用$watch
方法定义一个立即执行的监听器,演示immediate
选项的使用。
模板:
- 显示当前的计数。
- 提供一个按钮来增加计数。
- 显示监听器的输出日志。
2. 使用组合式 API 的示例
<template>
<div>
<h2>计数器(组合式 API)</h2>
<p>当前计数: {{ count }}</p>
<button @click="increment">增加</button>
<p>监听器输出:</p>
<ul>
<li v-for="(log, index) in logs" :key="index">{{ log }}</li>
</ul>
</div>
</template>
<script>
import { ref, watch, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
const logs = ref([]);
// 定义监听器
const stopWatch = watch(count, (newVal, oldVal) => {
logs.value.push(`count 从 ${oldVal} 变为 ${newVal}`);
fetchData(newVal);
}, {
immediate: false,
deep: false
});
// 方法定义
const increment = () => {
count.value++;
};
const fetchData = async (val) => {
const data = await new Promise((resolve) => {
setTimeout(() => {
resolve(`服务器返回的数据: ${val}`);
}, 1000);
});
logs.value.push(data);
};
// 立即执行一次监听器
onMounted(() => {
watch(
() => count.value,
(newVal, oldVal) => {
logs.value.push(`[立即监听] count 从 ${oldVal} 变为 ${newVal}`);
},
{ immediate: true }
);
});
return {
count,
increment,
logs
};
}
};
</script>
<style scoped>
button {
margin-top: 10px;
}
ul {
list-style-type: none;
padding: 0;
}
li {
background: #f0f0f0;
margin: 5px 0;
padding: 5px;
}
</style>
解释:
导入:
ref
:用于定义响应式数据。watch
:用于定义监听器。onMounted
:生命周期钩子,用于在组件挂载后执行代码。
数据定义:
count
:当前的计数,使用ref
定义。logs
:用于存储监听器输出的日志。
监听器:
- 使用
watch
函数定义count
的监听器。当count
变化时,回调函数会执行,将新旧值记录到logs
中,并调用fetchData
方法模拟异步操作。
- 使用
方法:
increment
:增加count
的值。fetchData
:模拟一个异步请求,延迟 1 秒后返回服务器数据。
生命周期钩子:
onMounted
:在组件挂载后,使用watch
函数定义一个立即执行的监听器,演示immediate
选项的使用。
模板:
- 显示当前的计数。
- 提供一个按钮来增加计数。
- 显示监听器的输出日志。
三、完整案例:购物车监听
<template>
<div>
<h1>购物车</h1>
<div>
<button @click="addItem">添加商品</button>
<button @click="removeItem">移除商品</button>
<button @click="changeUser">更改用户</button>
</div>
<div>
<h3>购物车内容 ({{ cartItems.length }} 件):</h3>
<ul>
<li v-for="(item, index) in cartItems" :key="index">
{{ item.name }} - ¥{{ item.price }} × {{ item.quantity }}
</li>
</ul>
</div>
<div>
<h3>总价: ¥{{ totalPrice }}</h3>
</div>
<div>
<h3>用户信息:</h3>
<p>姓名: {{ user.name }}</p>
<p>会员等级: {{ user.level }}</p>
</div>
<div v-if="discountApplied">
<p style="color: green;">已应用 {{ discountRate * 100 }}% 会员折扣!</p>
</div>
<div v-if="cartChanged">
<p style="color: orange;">购物车内容已更改,请确认!</p>
</div>
</div>
</template>
<script>
import { ref, reactive, computed, watch, watchEffect } from 'vue'
export default {
setup() {
// 购物车商品
const cartItems = ref([
{ id: 1, name: '商品A', price: 100, quantity: 1 },
{ id: 2, name: '商品B', price: 200, quantity: 2 }
])
// 用户信息
const user = reactive({
name: '张三',
level: 'gold', // silver, gold, platinum
discountRates: {
silver: 0.95,
gold: 0.9,
platinum: 0.85
}
})
// 计算属性
const totalPrice = computed(() => {
const subtotal = cartItems.value.reduce(
(sum, item) => sum + item.price * item.quantity, 0
)
return subtotal * discountRate.value
})
const discountRate = computed(() => {
return user.discountRates[user.level] || 1
})
// 状态标志
const discountApplied = ref(false)
const cartChanged = ref(false)
// 1. 监听用户等级变化,应用折扣
watch(
() => user.level,
(newLevel) => {
console.log(`用户等级变更为: ${newLevel}`)
discountApplied.value = true
// 3秒后隐藏折扣提示
setTimeout(() => {
discountApplied.value = false
}, 3000)
}
)
// 2. 深度监听购物车变化
watch(
cartItems,
(newItems, oldItems) => {
console.log('购物车内容变化:', newItems)
cartChanged.value = true
// 5秒后重置变化标志
setTimeout(() => {
cartChanged.value = false
}, 5000)
},
{ deep: true }
)
// 3. 使用 watchEffect 自动跟踪依赖
const stopEffect = watchEffect(() => {
console.log(`当前总价: ${totalPrice.value} (用户: ${user.name}, 等级: ${user.level})`)
})
// 方法
const addItem = () => {
const newId = cartItems.value.length + 1
cartItems.value.push({
id: newId,
name: `商品${String.fromCharCode(64 + newId)}`,
price: Math.round(Math.random() * 200 + 50),
quantity: 1
})
}
const removeItem = () => {
if (cartItems.value.length > 0) {
cartItems.value.pop()
}
}
const changeUser = () => {
const levels = ['silver', 'gold', 'platinum']
const currentIndex = levels.indexOf(user.level)
const nextIndex = (currentIndex + 1) % levels.length
user.level = levels[nextIndex]
// 随机更改用户名
const names = ['张三', '李四', '王五', '赵六']
user.name = names[Math.floor(Math.random() * names.length)]
}
return {
cartItems,
user,
totalPrice,
discountApplied,
cartChanged,
addItem,
removeItem,
changeUser
}
}
}
</script>
<style>
/* 简单样式 */
button {
margin: 5px;
padding: 8px 16px;
cursor: pointer;
}
</style>
四、总结
Vue 3 提供了强大的监听属性功能,使得开发者能够灵活地响应数据的变化并执行相应的逻辑。通过选项式 API 和组合式 API 两种方式,开发者可以根据具体需求选择最适合的方式来定义监听器。此外,监听器还支持多种选项,如 immediate
和 deep
,进一步增强了其功能。