父组件向子组件传参时,传递数组和对象类型的参数的方法

在 Vue3 中,父组件向子组件传递数组 / 对象类型的参数,核心逻辑和基础类型(字符串 / 数字)一致(Props 传递),但需注意「引用类型的默认值写法」「单向数据流」「参数校验」等特殊点,核心原则有以下三点:

  1. 数组 / 对象属于引用类型,父组件传递的是「引用地址」,子组件修改内部属性会直接影响父组件数据(违背单向数据流,需避免);
  2. 子组件声明数组 / 对象类型的 Props 时,默认值必须用函数返回(避免多个组件实例共享同一个引用);
  3. 传递方式和基础类型一致:父组件用 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([{...}, {...}])" 校验数组内对象的必填属性
相关推荐
一 乐1 小时前
餐厅管理智能点餐系统|基于java+ Springboot的餐厅管理系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端
前端一课1 小时前
【前端每天一题】🔥 第 9 题:防抖(debounce)与节流(throttle)的区别?如何实现?
前端·面试
前端一课1 小时前
【前端每天一题】🔥 第 10 题:浅拷贝 vs 深拷贝?如何手写深拷贝?
前端·面试
前端一课2 小时前
【前端每天一题】🔥 第 8 题:什么是事件委托?它的原理是什么?有哪些优点和常见坑? - 前端高频面试题
前端·面试
前端一课2 小时前
【前端每天一题】🔥第7题 事件冒泡与事件捕获 - 前端高频面试题
前端·面试
前端一课2 小时前
【前端每天一题】 第 5 题:Promise.then 执行顺序深入题(微任务队列机制)
前端·面试
前端一课2 小时前
【前端每天一题】🔥 事件循环第 6 题:setTimeout(fn, 0) 执行时机详解
前端·面试
前端一课2 小时前
【前端每天一题】🔥 第3题 事件循环 20 道经典面试题(附详细答案)
前端·面试
前端一课2 小时前
【前端每天一题】第 2 题:var、let、const 的区别?(绝对高频)
前端·面试