Code前端首页关于Code前端联系我们

Vue2 里怎么监听 props 变化?不同场景该用啥方法?

terry 9小时前 阅读数 11 #Vue
文章标签 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 反而麻烦——得同时监听 discountprice,不如 computed 自动处理依赖香。

再比如 props 是个对象 userInfo,只想监听 name 变化,用 computed 拆出来:

computed: {
  userName() {
    return this.userInfo.name
  }
},
watch: {
  userName(newVal) {
    console.log('名字变了:', newVal)
  }
}

这样既避开了深度监听的性能问题,又能精准捕捉 name 的变化,比直接 deep 监听 userInfo 聪明多了。

生命周期钩子结合判断

createdmounted 里先拿 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') // 通知父组件
    }
  }
}

这种写法适合逻辑特别复杂,watchcomputed 搞不定的情况,但日常开发里优先度低于前两种——毕竟 updated 太“泛”,容易把不相关的变化也当成 props 变化处理。

不同场景该选哪种方法?

方法没有绝对好坏,得看场景匹配度。

单纯“props 变了就执行逻辑”→选 watch

比如父组件切换 tab,传个 tabKey 给子组件,子组件要重新请求该 tab 下的数据,这时候用 watchtabKey,变化时调接口,逻辑清晰:

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 变了还要更新图表,这时候可以把逻辑封装成函数,mountedwatch 都调用:

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 指向问题

如果用箭头函数写 watchthis 会指向外部作用域(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
  }
}

或者用 lodashdebounce 做防抖,避免短时间内重复请求。

实战案例:父组件切换主题,子组件监听 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前端网发表,如需转载,请注明页面地址。

发表评论:

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

热门