我们知道,在 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:传给子组件的参数值为普通变量
包含两个含义:
- 参数类型是基本类型
- 参数值不是响应式变量
父组件
<InnerContent :data="plain"></InnerContent>
let plain = 2;
setTimeout(() => {
plain = 77;
}, 3000);
子组件(下同)
<template>
<div>
<span>props value:{{ props.data + 2 }}</span>
</div>
</template>
效果如下
场景 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);
运行效果
场景 3:传给子组件的参数值为响应式对象
代码示例
父组件
<InnerContent :data="childProps.data"></InnerContent>
const data = ref(2);
const childProps = ref({
data, // 这里的data指向一个ref响应式变量,改成非响应式的属性 data: 2 也一样
});
setTimeout(() => {
childProps.value.data = 11;
}, 3000);
无论响应式对象中的属性是不是响应式变量,子组件都能接收到新的参数值
场景 4:传给子组件的参数值为普通对象下的普通属性
简而言之,就是:
- 有一个普通对象(非 ref 响应式对象)
- 普通对象下有一个属性,它的值也是普通变量,不是 ref 响应式变量
代码示例
父组件
<InnerContent :data="childStaticProps.data"></InnerContent>
const childStaticProps = {
data: 2,
};
setTimeout(() => {
childStaticProps.data = 66;
}, 3000);
当父组件修改了对象的属性时,子组件能拿到新的参数值。
场景 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>
如果 template 的{{}}中需要对 props.data 做处理,则无法自动求值。
如果在 template 的{{}}中,props.data 后面加上.value,则子组件又正常显示了
<div>props value:{{ props.data.value + 2 }}</div>
不建议这样写,原因:
子组件强制需要传入一个含有 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);
无论响应式对象中的属性是不是响应式变量,子组件都能接收到新的参数值当父组件修改了对象的属性时,子组件能拿到新的参数值
场景 2:传给子组件的参数值为普通对象下的普通属性
父组件
<component
ref="componentRef"
:is="InnerContent"
v-bind="childStaticProps"
></component>
const childStaticProps = {
data: 2,
};
setTimeout(() => {
childStaticProps.data = 66;
}, 3000);
当父组件修改了对象的属性时,子组件能拿到新的参数值。
场景 3:传给子组件的参数值为普通对象下的响应式属性
传入子组件的是一个非响应式对象里面的属性。 例子如下:
父组件
<component
ref="componentRef"
:is="InnerContent"
v-bind="childWrongProps"
></component>
const childWrongProps = {
data,
};
setTimeout(() => {
childStaticProps.data = 66;
}, 3000);
子组件
- 如果子组件的 template 中,在{{}}中直接获取 props.data
<div>props value:{{ props.data }}</div>
还是可以正常显示
- 如果在{{}}中进行其他计算,就无法正常显示
e.g. template中这样写:
<div>props value:{{ props.data + 2 }}</div>
效果如下:
- 如果在后面加上.value,则子组件又正常显示了
<div>props value:{{ props.data.value + 2 }}</div>
效果
不建议这么写,原因同上。
总结
- template 中不是任何时候都能自动求值的。在组合式 API 中,父子组件传参时,如果传入一个普通对象,对象中有一个属性指向一个响应式变量,则在子组件的 template 中需要使用.value 获取值,Vue 不会自动求值。
- 不建议往一个普通对象中添加一个响应式属性,然后传给子组件,这样会和.value 产生耦合。可以把最外层的对象变成 ref 响应式对象。