新同事提出一个问题,为什么父组件传props到子组件,父组件中属性值是onMounted中通过接口异步获取的,子组件获取不到,得用watch监听prop变化,我看了他的代码,发现是ref和toRef、toRefs使用上的问题
想要实现的功能想要实现的功能
父组件传prop给子组件,子组件v-for用列表展示prop,并且可以对列表数据进行增删,父组件会通过接口获取值改变子组件的展示
问题代码
1 | <!-- 父组件 --> |
1 | <!-- 子组件 --> |
- 他的问题:父组件的
originDateList
改变后,子组件获取的originDateList
没有变,没有触发重新渲染 - 他的解决方法:使用
watch
监听originDateList
,有变化时去改dateForm
1
2
3
4
5
6
7
8// 子组件
watch(
() => props.originDateList,
(val) => {
dateForm.value = val;
},
{ deep: true }
); - 全部都走watch,那这vue3设计也忒不合理,明显不是这个解决方法
实际产生问题的原因
- 首先要明确一点,父组件传值变化时,子组件
props
也会相应地发生变化。在 Vue 3 中,默认情况下,组件的 props 是响应式的- 所以如果不需要对prop做修改,纯展示,那直接用prop也行
- 但他需要增删列表,自然也需要新建一个
ref对象
避免修改到prop,这里他用了ref()
也就导致了问题
- 出问题的是将 prop
originDateList
赋值给 ref对象dateForm
这个操作,实际上建立响应式关系的变成了prop值
和ref对象
,而我们期望建立同步关系的是prop
和ref对象
- 使用
ref()
是将props.originDateList
的值复制给dateForm.value
了,此后dateForm.value
的值与props.originDateList
的值是独立的,这意味着当props.originDateList
的值发生变化时,dateForm.value
的值不会自动更新,因为它们不是同一个引用 - 你需要手动更新
dateForm.value
,以确保它与props.originDateList
的值保持同步,这也是为什么他使用watch解决的原因
- 使用
- 但是实际上,父组件
originDateList
发生变化时,props.originDateList
是能监听到变化的,只是 ref对象dateForm
不行,因为它只和初始时props.originDateList
的属性值产生响应关系 - 所以借助
toRef
、toRefs
建立prop
和ref对象
之间的响应式引用就能解决问题
正确解决方案
方案1
使用 toRef
建立和 prop源数据 之间的响应关系,以此在父组件prop变化时获取新的值
1 | <!-- 子组件 --> |
方案2
使用 toRefs
与props中每个prop源数据都建立响应关系
1 | <!-- 子组件 --> |