vue3 setup 如何watch defineProps?

767 阅读2分钟

最近写一个vue单文件的组件时遇到了监听失败的问题,总结一下:

一、直接解构defineProps

子组件 Child.vue

"vue": "^3.4.15"

<template>
    <h1>子组件</h1>
    <div>{{count}}</div>
</template>
<script setup>
import {watch} from 'vue'
const {count,data} = defineProps(['count','data'])
 
watch(
  () => count,
  () => {
    console.log('watch count=', count)
  },
  {
    deep: true,
    immediate: true,
  }
)
watch(
  () => data.count,
  () => {
    console.log(' watch data=', data.count)
  },
  {
    deep: true,
    immediate: true,
  }
)
</script>

父组件 Parent.vue

<template>
    <h1>父组件</h1>
    <Child count={count}/>
    <button @click="onClick">点击</button>
</template>
<script setup>
import {ref,reactive} from 'vue'
import Child form './Child.vue'
const count = ref(0)
const data = reactive({count:0})
const onClick = ()=>{
    count.value = count.value + 1
    data.count = data.count + 1
}
</script>

结果:

  1. 页面可以渲染出count 和 data.count
  2. watch 只能监控到data.count的变化

原因:

  1. defineProps 返回的是 响应式 Proxy 对象
  2. 解构 = 直接取值,相当于把值快照到普通变量
  3. count 变成了普通常量,不再关联 Proxy
  4. 后续父组件更新,这个变量不会变,watch 自然监听不到

为什么 data.count 能监听到?

  • data 是父组件传的 reactive 对象
  • 解构后拿到的是对象引用
  • 访问 data.count 依然走 对象自身的响应式
  • 所以能监听到
  • 这是巧合,不是正确用法!

二、使用props

子组件 Child.vue

<script setup>
import {watch} from 'vue'
const props = defineProps(['count','data'])
watch(
  () => props.count,
  () => {
    console.log('watch count=', count)
  },
  {
    deep: true,
    immediate: true,
  }
)
watch(
  () => props.data.count,
  () => {
    console.log(' watch data=', data.count)
  },
  {
    deep: true,
    immediate: true,
  }
)
</script>

结果:

  1. 页面可以渲染出count 和 data.count
  2. watch 可以监控到props.count 和 props.data.count的变化

三、使用 torefs 结构 props

子组件 Child.vue

<script setup>
import {toRefs,watch} from 'vue'
const props = defineProps(['count','data'])
const {count,data} = toRefs(props)
watch(
  () => count,
  () => {
    console.log('watch count=', count.value)
  },
  {
    deep: true,
    immediate: true,
  }
)
watch(
  () => data.count,
  () => {
    console.log(' watch data=', data.count)
  },
  {
    deep: true,
    immediate: true,
  }
)
watch(
  () => data,
  () => {
    console.log(' watch data=', data.value)
  },
  {
    deep: true,
    immediate: true,
  }
)
</script>

结果:

  1. 页面可以渲染出count 和 data.count
  2. watch 可以监控到count的变化,但是不能监测到data.count
  3. 此时count和data转换为了ref类型count和data的值需要用.value 读取出来

原因:

  1. toRefs 把 props 里的每个属性变成 独立 ref

    • count → ref 对象
    • data → ref 对象(里面包裹着原来的 reactive 对象)
  watch(() => data.count, ...)

监听的是一个普通值,不是 ref,也不是 getter 不会建立依赖,所以监听不到

正确写法应该是:

watch(
  () => data.value.count,
  () => { ... },
  { deep: true }
)

或者直接监听 data 这个 ref:

watch(data, () => { ... }, { deep: true })