vue3 props传参 ref() 响应式无效的问题

新同事提出一个问题,为什么父组件传props到子组件,父组件中属性值是onMounted中通过接口异步获取的,子组件获取不到,得用watch监听prop变化,我看了他的代码,发现是ref和toRef、toRefs使用上的问题

想要实现的功能想要实现的功能

父组件传prop给子组件,子组件v-for用列表展示prop,并且可以对列表数据进行增删,父组件会通过接口获取值改变子组件的展示

问题代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- 父组件 -->
<template>
<Custom :originDateList="originDateList" />
</template>

<script setup>
import { getUserConfigSelectionRequest } from "@/api";
import Custom from "./custom";

let originDateList = ref([]);
const getDateComposition = async () => {
const res = await getUserConfigSelectionRequest({ type: "expirationDate" });
originDateList.value = res.data
};
onMounted(async() => await getDateComposition());
</script>
1
2
3
4
5
6
7
8
9
10
11
12
<!-- 子组件 -->
<template>
<div class="divItem" v-for="(item, index) in dateForm" :key="index">
...
</div>
<template>

<script setup>
const props = defineProps(["originDateList"]);
const dateForm = ref([]);
dateForm.value = props.originDateList
</script>
  • 他的问题:父组件的 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对象,而我们期望建立同步关系的是 propref对象
    • 使用 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 的属性值产生响应关系
  • 所以借助 toReftoRefs 建立 propref对象 之间的响应式引用就能解决问题

正确解决方案

方案1

使用 toRef 建立和 prop源数据 之间的响应关系,以此在父组件prop变化时获取新的值

1
2
3
4
5
6
7
8
9
10
<!-- 子组件 -->
<template>
<div class="divItem" v-for="(item, index) in originDateList" :key="index">
...
</div>
<template>

<script setup>
const originDateList = toRef(props, 'originDateList');
</script>

方案2

使用 toRefs 与props中每个prop源数据都建立响应关系

1
2
3
4
5
6
7
8
9
10
<!-- 子组件 -->
<template>
<div class="divItem" v-for="(item, index) in originDateList" :key="index">
...
</div>
<template>

<script setup>
const { originDateList } = toRefs(props);
</script>

参考

,