Vue3中监听props变化的三种实践方案

7 次阅读

Vue3中监听props变化是组件通信中非常常见的需求。在实际项目开发中,父组件经常会通过props向子组件传递数据,当父组件数据发生变化时,子组件需要及时响应,例如重新请求接口、更新表单内容、重新计算数据等。

Vue3提供了多种监听props变化的方法,不同场景下适合使用不同方案。合理选择监听方式,可以提高组件的可维护性,避免不必要的性能消耗。

一、使用watch监听props变化

watch是Vue3中最常用的数据监听方案之一,它可以监听响应式数据的变化,并在数据更新后执行指定逻辑。

在组件中接收props后,可以直接通过watch监听对应属性。

示例:

vue
 setup>
import { watch } from 'vue'

const props = defineProps({
  userInfo: {
    type: Object,
    default: () => ({})
  }
})

watch(
  () => props.userInfo,
  (newValue, oldValue) => {
    console.log('props发生变化')
    console.log('新数据:', newValue)
    console.log('旧数据:', oldValue)
  }
)

这里使用函数形式返回props.userInfo,这是Vue3监听props时推荐的写法。

原因在于:

JavaScript
watch(props.userInfo, callback)

这种写法实际上监听的是对象本身,而不是响应式引用,容易导致监听失效。

正确方式:

JavaScript
watch(
  () => props.userInfo,
  callback
)

通过getter函数获取数据,可以保持响应式追踪。

监听基本类型props

如果props传递的是字符串、数字、布尔值等基本类型:

vue
 setup>
import { watch } from 'vue'

const props = defineProps({
  count: Number
})

watch(
  () => props.count,
  (value) => {
    console.log('count变化:', value)
  }
)

当父组件修改:

JavaScript
count.value++

子组件中的watch会自动触发。

深度监听对象变化

默认情况下,watch监听对象只会监听引用变化:

JavaScript
props.userInfo = {
  name: 'Tom'
}

可以触发监听。

但是:

JavaScript
props.userInfo.name = 'Jack'

不会触发。

如果需要监听对象内部属性变化,需要开启deep:

JavaScript
watch(
  () => props.userInfo,
  (value) => {
    console.log(value)
  },
  {
    deep: true
  }
)

深度监听适合复杂对象,但需要注意性能问题。对象层级越深,Vue需要遍历的数据越多。


二、使用watchEffect自动监听props变化

除了watch之外,Vue3还提供了watchEffect。

它与watch最大的区别是:

  • watch需要明确指定监听目标

  • watchEffect会自动收集依赖

示例:

vue
 setup>
import { watchEffect } from 'vue'

const props = defineProps({
  title: String
})

watchEffect(() => {
  console.log('当前标题:', props.title)
})

当props.title发生变化时,watchEffect会重新执行。

这种方式代码更加简洁。

例如:

JavaScript
watchEffect(() => {
  document.title = props.title
})

当父组件修改标题时,页面标题会自动同步。

watchEffect适合哪些场景?

watchEffect更适合以下情况:

1. 依赖数据较多

例如:

JavaScript
watchEffect(() => {
  console.log(
    props.name,
    props.age,
    props.address
  )
})

如果使用watch,需要:

JavaScript
watch(
  [
    () => props.name,
    () => props.age,
    () => props.address
  ],
  callback
)

watchEffect更加简洁。

2. 副作用逻辑

例如:

  • 修改DOM

  • 请求接口

  • 更新第三方插件状态

示例:

JavaScript
watchEffect(async () => {
  const result = await getData(props.id)
  data.value = result
})

三、使用computed派生状态监听props

很多情况下,并不需要直接监听props,而是根据props计算新的状态。

这时候使用computed更加合适。

例如:

父组件:

vue
 :status="userStatus" />

子组件:

vue
 setup>
import { computed } from 'vue'

const props = defineProps({
  status: String
})

const statusText = computed(() => {
  if (props.status === 'online') {
    return '在线'
  }

  return '离线'
})

模板:

HTML
{{ statusText }}

当props变化时,computed会自动重新计算。

相比watch:

JavaScript
watch(
  () => props.status,
  value => {
    statusText.value = value === 'online'
      ? '在线'
      : '离线'
  }
)

computed更加符合Vue的数据驱动思想。

computed适合处理什么?

主要包括:

  • 数据格式转换

  • 状态映射

  • 数据过滤

  • 数据计算

例如:

JavaScript
const fullName = computed(() => {
  return props.firstName + props.lastName
})

无需额外维护变量,也不会产生同步问题。


四、监听props变化时的常见错误

1. 直接解构props导致响应式丢失

错误写法:

JavaScript
const { name } = props

watch(
  () => name,
  () => {
    console.log(name)
  }
)

因为解构后,name只是普通变量,不再具备响应式。

解决方法:

使用toRefs:

JavaScript
import { toRefs } from 'vue'

const props = defineProps({
  name: String
})

const { name } = toRefs(props)

watch(
  name,
  value => {
    console.log(value)
  }
)

2. 修改props数据

Vue规定:

子组件不能直接修改props。

错误:

JavaScript
props.name = 'new name'

会出现:

Unexpected mutation of prop

正确方式:

通过事件通知父组件:

JavaScript
emit('update:name', value)

或者使用v-model实现双向绑定。


3. watch没有设置立即执行

默认情况下:

JavaScript
watch(source, callback)

只会在数据变化后执行。

如果希望组件初始化时执行一次:

JavaScript
watch(
  () => props.id,
  value => {
    loadData(value)
  },
  {
    immediate: true
  }
)

这样组件加载时就会调用一次。


五、watch、watchEffect、computed如何选择?

实际开发中,可以根据需求选择:

方法使用场景特点
watch明确监听某个props变化控制精确,可获取新旧值
watchEffect多个响应式数据自动监听自动收集依赖,代码简洁
computed根据props计算新数据无副作用,性能更好

简单判断:

如果需要知道变化前后的值:

选择:

JavaScript
watch()

例如:

JavaScript
watch(
  () => props.id,
  (newId, oldId) => {
    console.log(oldId,newId)
  }
)

如果只是根据props变化执行同步逻辑:

选择:

JavaScript
watchEffect()

如果只是生成新的显示数据:

选择:

JavaScript
computed()

六、实际项目中的推荐实践

在大型Vue3项目中,推荐遵循以下原则:

1. 优先使用computed处理数据转换

不要为了改变显示内容而使用watch。

例如:

不推荐:

JavaScript
watch(
  () => props.price,
  value => {
    priceText.value = '¥' + value
  }
)

推荐:

JavaScript
const priceText = computed(() => {
  return '¥' + props.price
})

2. 接口请求使用watch

例如根据用户ID获取详情:

JavaScript
watch(
  () => props.userId,
  async id => {
    const data = await getUserInfo(id)
    user.value = data
  },
  {
    immediate:true
  }
)

这种场景watch更加清晰。


3. 避免滥用deep监听

深度监听虽然方便,但可能影响性能。

例如:

JavaScript
watch(
  () => props.formData,
  handler,
  {
    deep:true
  }
)

如果表单数据非常复杂,每次修改都会触发完整检查。

更好的方式是监听具体字段:

JavaScript
watch(
  () => props.formData.name,
  value => {
    console.log(value)
  }
)

七、Vue3监听props变化总结

Vue3监听props变化主要有三种实践方案:

第一种是watch,适合监听明确的数据变化,可以获取新值和旧值,是项目中最常用的方法。

第二种是watchEffect,适合根据多个响应式数据自动执行副作用逻辑,代码更加简洁。

第三种是computed,适合根据props派生新的状态,是Vue推荐的数据处理方式。

实际开发时,应根据业务需求选择合适方案。需要执行异步操作、监听具体变化时使用watch;需要自动响应多个依赖时使用watchEffect;需要转换数据时优先考虑computed。正确使用这三种方式,可以让Vue3组件通信更加稳定、高效。