Vue2 里怎么监听 props 变化?不同场景该用啥方法?
不少刚学 Vue2 的同学碰到父组件传值给子组件后,子组件要根据 props 变化做逻辑处理时,总会犯难——咋监听 props 变化啊?不同场景选啥方式更合适?这篇就把 Vue2 里监听 props 的门道拆明白,从为啥要监听、具体咋实现到踩坑点全讲透。
为啥要监听 props 变化?
先想场景:父组件是商品列表的筛选栏,传个筛选条件(比如价格区间、分类)给子组件的商品列表;或者父组件切换主题,传个 theme
给子组件改样式,这时候子组件得“感知” props 变了,才能重新请求数据、更新 UI。
要是不监听,子组件拿到 props 后只会在初始化时处理一次,后续父组件改了 props,子组件根本不知道,功能就“断档”了,比如筛选条件变了,商品列表还显示旧数据,用户体验直接崩掉,所以监听 props 是实现“父组件驱动子组件动态更新”的关键。
Vue2 监听 props 的常用方法有哪些?
Vue2 给了好几种方式,适合不同场景,咱一个个拆。
watch 选项直接监听
最直接的方式就是用 watch
选项,专门盯某个 props 的变化,写法长这样:
export default { props: ['propName'], watch: { propName(newVal, oldVal) { // newVal 是新值,oldVal 是旧值 console.log('props 变了,新值:', newVal, '旧值:', oldVal) // 执行逻辑,比如调接口、改 data 里的变量 } } }
要是 props 是对象/数组这种引用类型,只改内部属性(userInfo.name = '新名'
),Vue 默认监不到,这时候得开深度监听:
watch: { userInfo: { handler(newVal) { console.log('userInfo 里的属性变了', newVal.name) }, deep: true // 开启深度监听 } }
但深度监听有性能代价,因为 Vue 要递归遍历对象里的所有属性,所以尽量精准点,比如只监听对象里的某个属性,后面讲 computed
时会说到。
想让 watch
在组件初始化时就执行一次(比如刚加载就根据 props 请求数据),加个 immediate
:
watch: { searchKey: { handler(newVal) { this.fetchData(newVal) // 调接口 }, immediate: true // 组件创建后立刻执行一次 } }
用 computed 间接监听
如果要把 props 和其他数据结合,生成“衍生结果”,用 computed
更顺手,props 是折扣率 discount
,data 里存原价 price
,要显示折后价:
export default { props: ['discount'], data() { return { price: 100 } }, computed: { discountedPrice() { return this.price * this.discount } } }
模板里直接用 {{ discountedPrice }}
,props 或 data 变了,computed
会自动更新,这种场景下,用 watch
反而麻烦——得同时监听 discount
和 price
,不如 computed
自动处理依赖香。
再比如 props 是个对象 userInfo
,只想监听 name
变化,用 computed
拆出来:
computed: { userName() { return this.userInfo.name } }, watch: { userName(newVal) { console.log('名字变了:', newVal) } }
这样既避开了深度监听的性能问题,又能精准捕捉 name
的变化,比直接 deep
监听 userInfo
聪明多了。
生命周期钩子结合判断
created
、mounted
里先拿 props 初始化,然后在 updated
钩子(组件更新后触发,包括 props 变化导致的更新)里处理后续变化,但这种方式没那么直接,因为 updated
会在很多情况触发(data 里其他数据变了也会触发),容易误操作。
举个简单例子:子组件接收一个列表 list
,要缓存列表并在变化时通知父组件:
export default { props: ['list'], data() { return { cacheList: [] } }, mounted() { this.cacheList = [...this.list] // 初始化缓存 }, updated() { if (this.list.length !== this.cacheList.length) { this.cacheList = [...this.list] this.$emit('list-updated') // 通知父组件 } } }
这种写法适合逻辑特别复杂,watch
和 computed
搞不定的情况,但日常开发里优先度低于前两种——毕竟 updated
太“泛”,容易把不相关的变化也当成 props 变化处理。
不同场景该选哪种方法?
方法没有绝对好坏,得看场景匹配度。
单纯“props 变了就执行逻辑”→选 watch
比如父组件切换 tab,传个 tabKey
给子组件,子组件要重新请求该 tab 下的数据,这时候用 watch
盯 tabKey
,变化时调接口,逻辑清晰:
watch: { tabKey(newVal) { this.fetchTabData(newVal) // 调接口拿对应 tab 的数据 } }
要是初始化时也得调接口,加个 immediate: true
就行,一步到位。
要把 props 和其他数据结合出“衍生结果”→选 computed
比如购物车组件,props 传商品列表 goodsList
,data 存选中状态 selectedIds
,要显示“已选商品数量”:
computed: { selectedCount() { return this.goodsList.filter(good => this.selectedIds.includes(good.id)).length } }
模板里用 {{ selectedCount }}
,不管是 goodsList
变了,还是 selectedIds
变了,computed
都会自动更新,比用 watch
同时监听两个数据清爽多了。
初始化+后续变化都要处理,且逻辑复杂→watch + 生命周期
比如子组件是个图表,props 传配置 config
,初始化时要根据 config
渲染图表,之后 config
变了还要更新图表,这时候可以把逻辑封装成函数,mounted
和 watch
都调用:
export default { props: ['config'], methods: { renderChart(newConfig) { // 销毁旧图表、根据 newConfig 渲染新图表 } }, mounted() { this.renderChart(this.config) // 初始化渲染 }, watch: { config: 'renderChart' // 变化时重新渲染 } }
这样初始化和后续变化都能覆盖,还避免了代码重复。
监听 props 容易踩的坑有哪些?
知道方法还不够,得避开这些“暗坑”。
基础类型 vs 引用类型的坑
props 是字符串、数字这些基础类型,watch
能直接检测变化;但对象、数组是引用类型,父组件直接改内部属性(userInfo.name = '新名'
),子组件 watch
不触发——因为引用地址没改,Vue 以为没变化。
解决办法有两种:要么父组件修改时“换引用”(userInfo = { ...oldUserInfo, name: '新名' }
),要么子组件用 deep: true
监听,但 deep
监听性能差,所以更推荐父组件规范修改方式,子组件用 computed
拆属性监听(前面讲过的 userName
例子)。
watch 里的 this 指向问题
如果用箭头函数写 watch
,this
会指向外部作用域(window
),导致拿不到组件实例:
// 错误写法!this 不对 watch: { propName: (newVal) => { this.doSomething() // this 不是组件实例,会报错 } }
改成普通函数就好:
// 正确写法 watch: { propName(newVal) { this.doSomething() // this 是组件实例 } }
重复监听导致逻辑重复执行
watch
里调接口,props 频繁变化时接口狂发,服务器直接崩了,这时候得加条件判断或防抖:
watch: { searchKey: { handler(newVal) { if (newVal.trim()) { // 输入非空才发请求 this.fetchData(newVal) } }, immediate: true } }
或者用 lodash
的 debounce
做防抖,避免短时间内重复请求。
实战案例:父组件切换主题,子组件监听 props 换样式
光说不练假把式,看个实际例子,父组件有个下拉框切换 theme
,传给子组件;子组件根据 theme
换 class 和样式,同时打印日志。
子组件代码:
<template> <div :class="themeClass">主题切换示例</div> </template> <script> export default { props: ['theme'], computed: { themeClass() { return this.theme === 'dark' ? 'dark-mode' : 'light-mode' } }, watch: { theme(newVal) { console.log('主题变了,现在是', newVal) // 这里还能加过渡动画、埋点等逻辑 } } } </script> <style> .dark-mode { background: #333; color: #fff; transition: all 0.3s; } .light-mode { background: #fff; color: #333; transition: all 0.3s; } </style>
这里用 computed
处理样式类(响应式更新 class),用 watch
处理额外逻辑(比如打印日志、加动画),结合起来既优雅又灵活,父组件只要传 theme
变化,子组件就能自动响应。
Vue2 里监听 props 得先想清楚场景——是单纯执行逻辑、做数据衍生还是复杂初始化+更新,watch 灵活处理变化触发逻辑,computed 适合数据转换,生命周期钩子适合补漏,同时避开引用类型、this 指向、重复执行这些坑,才能让组件间传值后的逻辑响应丝滑,多练几个不同场景的例子,比如表单联动、列表筛选、主题切换,就能掌握不同方法的精髓啦~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。