在 Vue3 中,父组件向子组件传递数组 / 对象类型的参数,核心逻辑和基础类型(字符串 / 数字)一致(Props 传递),但需注意「引用类型的默认值写法」「单向数据流」「参数校验」等特殊点,核心原则有以下三点:
- 数组 / 对象属于引用类型,父组件传递的是「引用地址」,子组件修改内部属性会直接影响父组件数据(违背单向数据流,需避免);
- 子组件声明数组 / 对象类型的 Props 时,默认值必须用函数返回(避免多个组件实例共享同一个引用);
- 传递方式和基础类型一致:父组件用
v-bind/:动态绑定,子组件用defineProps声明接收。
传递数组类型参数
步骤 1:子组件声明接收数组类型的 Props
子组件需指定 type: Array,并注意默认值的写法(函数返回空数组):
<!-- Child.vue -->
<template>
<div>
<h3>父组件传递的数组:</h3>
<ul>
<!-- 遍历数组渲染 -->
<li v-for="(item, index) in list" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
// 声明数组类型的Props(完整写法:指定类型、默认值、校验)
const props = defineProps({
// 数组类型
list: {
type: Array, // 指定类型为Array
required: false, // 非必传
default: () => [], // 数组默认值:必须用函数返回空数组(避免引用共享)
// 可选:自定义校验(比如校验数组长度)
validator: (value) => {
// 校验数组长度不超过10
return value.length <= 10;
}
}
});
// 访问数组(组合式API中直接通过props.list访问)
console.log('数组长度:', props.list.length);
console.log('数组第一项:', props.list[0]);
</script>
步骤 2:父组件传递数组给子组件
父组件通过 :list 动态绑定数组变量(静态传递无意义,数组需用变量):
<!-- Parent.vue -->
<template>
<div>
<!-- 动态绑定数组参数 -->
<Child :list="parentList" />
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import Child from './Child.vue';
// 方式1:用ref定义数组(推荐,ref支持所有类型)
const parentList = ref(['苹果', '香蕉', '橙子']);
// 方式2:用reactive定义数组(也可,需注意是引用类型)
// const parentList = reactive(['苹果', '香蕉', '橙子']);
// 父组件修改数组(子组件会同步更新,因为是引用类型)
setTimeout(() => {
parentList.value.push('葡萄'); // ref数组需通过.value修改
}, 2000);
</script>
三、传递对象类型参数
步骤 1:子组件声明接收对象类型的 Props
子组件指定 type: Object,默认值同样需用函数返回(返回默认对象):
<!-- Child.vue -->
<template>
<div>
<h3>父组件传递的对象:</h3>
<p>姓名:{{ user.name }}</p>
<p>年龄:{{ user.age }}</p>
<p>爱好:{{ user.hobbies.join('、') }}</p>
</div>
</template>
<script setup>
// 声明对象类型的Props
const props = defineProps({
user: {
type: Object, // 指定类型为Object
required: true, // 必传(根据业务调整)
// 对象默认值:函数返回默认对象(避免多个实例共享)
default: () => ({
name: '默认用户',
age: 18,
hobbies: ['读书']
}),
// 自定义校验(校验对象属性)
validator: (value) => {
// 必须包含name和age属性
return 'name' in value && 'age' in value;
}
}
});
// 访问对象属性
console.log('用户名:', props.user.name);
</script>
步骤 2:父组件传递对象给子组件
父组件通过 :user 动态绑定对象变量(推荐用 reactive 定义对象):
<!-- Parent.vue -->
<template>
<div>
<!-- 动态绑定对象参数 -->
<Child :user="parentUser" />
</div>
</template>
<script setup>
import { reactive } from 'vue';
import Child from './Child.vue';
// 用reactive定义对象(推荐,适合复杂引用类型)
const parentUser = reactive({
name: '张三',
age: 25,
hobbies: ['打球', '听歌']
});
// 父组件修改对象属性(子组件会同步更新)
setTimeout(() => {
parentUser.age = 26; // reactive对象直接修改,无需.value
parentUser.hobbies.push('看电影');
}, 2000);
</script>
四、传递「数组嵌套对象」的复杂参数
实际开发中常传递「数组 + 对象」的复合数据,写法和单一类型一致:
子组件声明
<!-- Child.vue -->
<template>
<div>
<h3>用户列表(数组嵌套对象):</h3>
<div v-for="(item, index) in userList" :key="index">
<p>ID:{{ item.id }}</p>
<p>姓名:{{ item.name }}</p>
</div>
</div>
</template>
<script setup>
const props = defineProps({
userList: {
type: Array,
default: () => [],
// 校验数组中的每一项都是对象,且包含id和name
validator: (value) => {
return value.every(item => typeof item === 'object' && item.id && item.name);
}
}
});
</script>
父组件传递
<!-- Parent.vue -->
<template>
<Child :userList="parentUserList" />
</template>
<script setup>
import { ref } from 'vue';
import Child from './Child.vue';
const parentUserList = ref([
{ id: 1, name: '张三' },
{ id: 2, name: '李四' }
]);
// 新增一条数据
setTimeout(() => {
parentUserList.value.push({ id: 3, name: '王五' });
}, 2000);
</script>
五、关键注意事项(避坑重点)
1. 单向数据流:子组件不要直接修改数组 / 对象的属性
虽然数组 / 对象是引用类型,子组件修改内部属性会直接影响父组件,但这违背 Vue 的单向数据流原则(易导致数据溯源困难)。正确做法:子组件通过自定义事件通知父组件修改:
<!-- Child.vue(子组件触发事件通知修改) -->
<template>
<button @click="handleUpdateUser">修改父组件的用户年龄</button>
</template>
<script setup>
const props = defineProps(['user']);
const emit = defineEmits(['update-user']);
const handleUpdateUser = () => {
// 子组件不直接修改props,而是触发事件传递新值
emit('update-user', { ...props.user, age: 30 });
};
</script>
<!-- Parent.vue(父组件监听事件并修改) -->
<template>
<Child :user="parentUser" @update-user="handleUpdateUser" />
</template>
<script setup>
import { reactive } from 'vue';
const parentUser = reactive({ name: '张三', age: 25 });
const handleUpdateUser = (newUser) => {
// 父组件修改源数据
Object.assign(parentUser, newUser);
};
</script>
2. 默认值必须用函数返回
数组 / 对象的默认值若直接写 default: [] 或 default: {},会导致所有组件实例共享同一个引用(修改一个实例的默认值,其他实例也会受影响)。正确写法:
// 错误(共享引用)
default: []
// 正确(函数返回新数组/对象)
default: () => []
// 错误
default: { name: '默认' }
// 正确
default: () => ({ name: '默认' })
3. 类型校验支持多个类型
若 Props 允许接收「数组或对象」类型,可将 type 设为数组:
const props = defineProps({
data: {
type: [Array, Object], // 支持数组或对象
default: () => []
}
});
4. 传递空数组 / 空对象
若父组件暂无数据,可传递空数组 / 空对象,子组件会使用默认值(若未设 required: true):
<!-- Parent.vue -->
<Child :list="[]" :user="{}" />
六、
| 类型 | 子组件声明核心 | 父组件传递核心 | 关键注意点 |
|---|---|---|---|
| 数组 | type: Array + default: () => [] |
:list="ref([]/[数据])" |
避免子组件直接修改数组元素 |
| 对象 | type: Object + default: () => ({}) |
:user="reactive({})" |
避免子组件直接修改对象属性 |
| 复合类型 | type: Array + 校验数组项 |
:list="ref([{...}, {...}])" |
校验数组内对象的必填属性 |