Vue3组合式API中的props踩坑

699 阅读5分钟

我们知道,在 Vue3 中,父子组件通过 Props 传参。在 script setup 组合式上下文中,可以定义 ref 响应式变量。

那么,如果给子组件传入响应式变量,会出现什么情况呢?

我们先分析一下常见的场景。

在 Vue3 中,创建子组件的方式有 2 种:

  • 静态组件:在 template 中引用子组件的标签
  • 动态组件:使用 <component> 标签的 is 参数,传入子组件,子组件的 Props 参数通过 v-bind 动态传入

props 参数可以是 ref 响应式对象,也可以是普通对象。

普通对象的属性,可以是普通变量,也可以是响应式变量。

那么,我们就有以下场景:

组件形式基本类型/对象类型最外层是否响应式对象内层是否响应式
静态组件基本类型响应式/
静态组件基本类型非响应式/
静态组件对象类型响应式响应式
静态组件对象类型响应式非响应式
静态组件对象类型非响应式响应式
静态组件对象类型非响应式非响应式
动态组件v-bind 的值为对象类型v-bind 的值为响应式响应式
动态组件v-bind 的值为对象类型v-bind 的值为响应式非响应式
动态组件v-bind 的值为对象类型v-bind 的值为非响应式响应式
动态组件v-bind 的值为对象类型v-bind 的值为非响应式非响应式

我们分别尝试以上场景。

静态组件

场景 1:传给子组件的参数值为普通变量

包含两个含义:

  1. 参数类型是基本类型
  2. 参数值不是响应式变量

父组件

<InnerContent :data="plain"></InnerContent>
let plain = 2;
setTimeout(() => {
  plain = 77;
}, 3000);

子组件(下同)

<template>
  <div>
    <span>props value:{{ props.data + 2 }}</span>
  </div>
</template>

效果如下

20250105_230714_image.png

场景 2:传给子组件的参数值为 ref 响应式变量

注:为了和场景 2 区分,这里为基本类型的响应式变量

代码如下

父组件

<template>
  <InnerContent :data="data"></InnerContent>
</template>
import { ref } from "vue";
import InnerContent from "./InnerContent.vue";

const data = ref(2);

子组件的 template 中,不需要对 props 下的参数添加 .value 代码。

当父组件的 data 值变化时,子组件内部能响应式更新。

父组件

setTimeout(() => {
  data.value = 11;
}, 3000);

运行效果

20250105_220805_image.png

场景 3:传给子组件的参数值为响应式对象

代码示例

父组件

<InnerContent :data="childProps.data"></InnerContent>
const data = ref(2);
const childProps = ref({
  data, // 这里的data指向一个ref响应式变量,改成非响应式的属性 data: 2  也一样
});

setTimeout(() => {
  childProps.value.data = 11;
}, 3000);

无论响应式对象中的属性是不是响应式变量,子组件都能接收到新的参数值

20250105_221208_image.png

场景 4:传给子组件的参数值为普通对象下的普通属性

简而言之,就是:

  1. 有一个普通对象(非 ref 响应式对象)
  2. 普通对象下有一个属性,它的值也是普通变量,不是 ref 响应式变量

代码示例

父组件

<InnerContent :data="childStaticProps.data"></InnerContent>
const childStaticProps = {
  data: 2,
};
setTimeout(() => {
  childStaticProps.data = 66;
}, 3000);

当父组件修改了对象的属性时,子组件能拿到新的参数值。

20250105_220359_image.png

场景 5:传给子组件的参数值为普通对象下的响应式属性

传入子组件的是一个非响应式对象里面的属性。 例子如下:

父组件

<template>
  <InnerContent :data="childWrongProps"></InnerContent>
</template>
const data = ref(2);
const childWrongProps = {
  data, // data的值 指向另一个叫data的ref响应式变量
  onChange,
};

子组件 如果 template 的{{}}中只访问 props.data,则正常显示。

<div>props value:{{ props.data }}</div>

20250105_214600_image.png

如果 template 的{{}}中需要对 props.data 做处理,则无法自动求值。

20250105_214630_image.png

如果在 template 的{{}}中,props.data 后面加上.value,则子组件又正常显示了

<div>props value:{{ props.data.value + 2 }}</div>

20250105_223350_image.png

不建议这样写,原因:

子组件强制需要传入一个含有 value 属性的对象进来,不通用。

动态组件

使用 component 标签,is 参数传入组件实例,v-bind 传入动态参数(包括事件回调)。

由于动态参数是通过对象的形式传入,所以我们只需要再尝试一下上面的对象形式的 3 个场景。

场景 1:传给子组件的参数值为响应式对象

父组件

<component
  ref="componentRef"
  :is="InnerContent"
  v-bind="childProps"
></component>
import InnerContent from "./InnerContent.vue";

const childProps = ref({
  data, // 这里的data指向一个ref响应式变量,改成非响应式的属性 data: 2  也一样
});
setTimeout(() => {
  childProps.value.data = 11;
}, 3000);

无论响应式对象中的属性是不是响应式变量,子组件都能接收到新的参数值当父组件修改了对象的属性时,子组件能拿到新的参数值

20250105_222429_image.png

场景 2:传给子组件的参数值为普通对象下的普通属性

父组件

<component
  ref="componentRef"
  :is="InnerContent"
  v-bind="childStaticProps"
></component>
const childStaticProps = {
  data: 2,
};
setTimeout(() => {
  childStaticProps.data = 66;
}, 3000);

当父组件修改了对象的属性时,子组件能拿到新的参数值。

20250105_222552_image.png

场景 3:传给子组件的参数值为普通对象下的响应式属性

传入子组件的是一个非响应式对象里面的属性。 例子如下:

父组件

<component
  ref="componentRef"
  :is="InnerContent"
  v-bind="childWrongProps"
></component>
const childWrongProps = {
  data,
};
setTimeout(() => {
  childStaticProps.data = 66;
}, 3000);

子组件

  1. 如果子组件的 template 中,在{{}}中直接获取 props.data
<div>props value:{{ props.data }}</div>

还是可以正常显示

20250105_231245_image.png

  1. 如果在{{}}中进行其他计算,就无法正常显示

e.g. template中这样写:

<div>props value:{{ props.data + 2 }}</div>

效果如下:

20250105_223221_image.png

  1. 如果在后面加上.value,则子组件又正常显示了
<div>props value:{{ props.data.value + 2 }}</div>

效果

20250105_223622_image.png

不建议这么写,原因同上。

总结

  1. template 中不是任何时候都能自动求值的。在组合式 API 中,父子组件传参时,如果传入一个普通对象,对象中有一个属性指向一个响应式变量,则在子组件的 template 中需要使用.value 获取值,Vue 不会自动求值。
  2. 不建议往一个普通对象中添加一个响应式属性,然后传给子组件,这样会和.value 产生耦合。可以把最外层的对象变成 ref 响应式对象。