Vue3里怎么watch props?要注意哪些点?
在Vue3开发中,不少同学做组件通信时,碰到父组件把数据传给子组件后,想在子组件里“盯住”props的变化做逻辑,却搞不清怎么用watch正确实现,比如子组件接收了父组件的商品列表`goodsList`,想在列表变化时重新渲染图表;或者父组件切换筛选条件,子组件要跟着刷新数据请求,这时候咋用watch监听props?监听过程又有哪些容易踩的坑?今天把Vue3 watch props的门道拆明白。
为啥子组件要watch props?
先想清楚场景:父组件的数据变化会触发子组件props更新,子组件得“响应”这种变化执行逻辑,常见场景比如:
- 数据驱动视图更新:父组件切换Tab,子组件接收Tab标识,变化时重新拉取对应Tab的接口数据;
- 配置项变化重置状态:父组件传给子组件的
config对象变了(比如分页大小从10改成20),子组件得重置分页器、重新请求数据; - 衍生数据计算:props是原始列表,子组件要根据列表变化,重新计算过滤后的列表、统计总数等。
要是不监听props,子组件内部逻辑就没法感知父组件传来的数据变化,要么页面不更新,要么得写重复代码在父组件里触发,代码耦合度超高。
Vue3里咋用watch监听props?
Vue3的watch需要配合setup语法(或语法糖)使用,核心是用getter函数指定要监听的props属性,再写回调处理变化,分几种常见情况讲:
监听单个props属性(基础用法)
子组件先通过defineProps声明接收的props,再用watch监听,举个例子,子组件接收父组件的userList,变化时打印日志:
<script setup>
import { watch } from 'vue'
// 声明接收的props
const props = defineProps(['userList'])
// 监听userList变化
watch(
() => props.userList, // getter函数,返回要监听的props属性
(newVal, oldVal) => { // 变化时的回调,newVal是新值,oldVal是旧值
console.log('用户列表变了:', newVal, oldVal)
// 这里写业务逻辑,比如重新请求接口、更新图表等
}
)
</script>
⚠️ 注意:不能直接写watch(props.userList, ...)!因为props.userList如果是字符串、数字这类基础类型,直接传的话,watch拿到的是初始值的“快照”,后续props变化时无法触发监听,必须用getter函数(箭头函数返回props.xxx),让Vue能跟踪响应式依赖。
监听多个props属性
如果要同时监听userList和page两个props,用数组包裹多个getter函数:
watch(
[() => props.userList, () => props.page],
([newList, newPage], [oldList, oldPage]) => {
// 当userList或page变化时,回调执行
console.log('列表或页码变了:', newList, newPage, oldList, oldPage)
}
)
数组里的每个getter对应一个props属性,回调的参数也是数组,按顺序对应新值和旧值。
监听整个props对象(谨慎使用)
如果非要监听所有props的变化(不推荐,性能差),可以直接传props给watch:
watch(
props,
(newProps, oldProps) => {
console.log('props里某个属性变了:', newProps, oldProps)
}
)
但这么做会监听props里所有属性的变化,哪怕只是一个无关紧要的属性修改,都会触发回调,除非你明确需要“任何props变化都执行逻辑”,否则尽量监听具体属性,减少不必要的性能消耗。
watch props时容易踩的坑
很多同学照着代码写,结果监听不到变化、重复触发或者性能爆炸,大概率是踩了这些坑:
getter函数写错,监听不到变化
最常见的错误是没写getter函数,直接传props.xxx。
// 错误写法!监听不到变化
watch(props.userList, (newVal) => { ... })
因为props.userList如果是基础类型(如字符串),watch一开始拿到的是初始值的拷贝,后续props更新时,这个拷贝不会自动同步,所以监听不到。必须用() => props.xxx这种getter形式,让Vue能跟踪props.xxx的响应式变化。
引用类型(对象/数组)的深层监听问题
如果props是对象或数组(引用类型),只改内部属性,引用没变化,watch默认不触发,比如父组件传{ name: '小明' },后来父组件只改name为'小红',子组件这样写监听不到:
// 父组件没替换整个对象,只是改了name,props.obj的引用没变
watch(() => props.obj, (newVal) => { ... }) // 不会触发
这时候有两种解决办法:
- 让父组件修改时替换整个对象(推荐):父组件把
obj改成{ ...obj, name: '小红' },这样引用变化,子组件watch能触发; - 子组件开启deep监听:给watch加
{ deep: true }选项,但性能开销大,谨慎用:watch(() => props.obj, (newVal) => { ... }, { deep: true })
初始化时是否执行回调?
watch默认是惰性的——组件初始化时不执行回调,只有props变化时才执行,如果需要初始化时就执行一次(比如第一次拿到props就请求数据),加immediate: true:
watch(() => props.userList, (newVal) => {
// 初始化时执行,之后userList变化也执行
console.log('初始化或变化时执行:', newVal)
}, { immediate: true })
watch和watchEffect,监听props选哪个?
Vue3里还有个watchEffect,和watch功能类似但用法不同,简单说:
- watch:需要明确指定监听的数据源(比如props.xxx),能拿到新旧值,默认惰性执行(可通过
immediate修改); - watchEffect:自动收集回调里用到的响应式数据(比如props.xxx)作为依赖,拿不到旧值,初始化时就会执行一次(非惰性)。
举个场景对比:
- 用watch:子组件需要对比props的新旧列表,找出新增元素,这时候必须用watch拿
oldVal和newVal; - 用watchEffect:子组件只要props.list变化,就重新计算“已完成任务数”,不需要旧值,用watchEffect更简洁:
watchEffect(() => { const doneCount = props.list.filter(item => item.done).length console.log('已完成任务数:', doneCount) })
实际项目的最佳实践
光会用还不够,得结合项目场景写高效、易维护的代码:
精准监听,避免性能浪费
- 别监听整个props对象,尽量监听具体属性(如
() => props.userList); - 引用类型优先让父组件替换整个对象(改引用),少用
deep监听; - 如果要监听对象内部属性,直接写
() => props.obj.name,比deep监听整个obj高效。
结合computed处理衍生数据
如果props变化后,需要先做数据转换(比如过滤、排序),再执行逻辑,建议用computed先处理数据,再watch computed结果:
<script setup>
import { watch, computed } from 'vue'
const props = defineProps(['rawList'])
// 先用computed过滤出已完成的任务
const doneList = computed(() => props.rawList.filter(item => item.done))
// 再watch过滤后的列表
watch(doneList, (newDoneList) => {
console.log('已完成列表变化:', newDoneList)
// 基于doneList做渲染、请求等逻辑
})
</script>
这样分工明确:computed负责数据转换,watch负责响应变化,代码更清晰。
别过度依赖watch,试试其他通信方式
有时候props变化后要触发子组件方法,不一定非用watch,比如父组件通过ref调用子组件暴露的方法:
<!-- 子组件 -->
<script setup>
const handleRefresh = () => {
// 刷新逻辑
}
defineExpose({ handleRefresh })
</script>
<!-- 父组件 -->
<template>
<Child ref="childRef" :list="list" />
</template>
<script setup>
const childRef = ref()
const changeList = () => {
list.value = [...newList]
childRef.value.handleRefresh() // 父组件主动调用子组件方法
}
</script>
这种方式适合父组件主动控制子组件逻辑的场景,和watch(子组件被动响应props)互补。
Vue3里watch props的核心是用getter函数指定监听目标,避开“没写getter、深层监听滥用、初始化执行”这些坑,再结合computed、watchEffect等工具优化逻辑,只要把这些细节吃透,不管是简单的列表更新,还是复杂的配置项响应,都能优雅处理~要是你还有其他关于Vue3组件通信的疑问,评论区随时聊~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
code前端网


发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。