Vue3 中父子组件传参是组件通信的核心场景,需遵循「父传子靠 Props,子传父靠自定义事件」的原则,以下是资料总结

一、父组件向子组件传参(Props)

核心逻辑

父组件通过自定义属性传递数据,子组件通过 defineProps(组合式 API)/ props 选项(选项式 API)声明接收,Props 是单向数据流(子组件不能直接修改父组件传递的 Props,需通过事件通知父组件修改)。

1. 基础用法(组合式 API / <script setup>

步骤 1:子组件声明接收 Props

子组件(如 Child.vue)通过 defineProps 声明要接收的参数,支持指定类型、默认值、校验规则:

复制代码
<!-- Child.vue -->
<template>
  <div>
    <!-- 直接使用Props -->
    <p>父组件传递的字符串:{{ msg }}</p>
    <p>父组件传递的数字:{{ count }}</p>
    <p>父组件传递的对象:{{ user.name }} - {{ user.age }}</p>
  </div>
</template>

<script setup>
// 方式1:简洁写法(仅指定类型)
const props = defineProps(['msg', 'count', 'user']);

// 方式2:完整写法(指定类型、默认值、校验,推荐)
const props = defineProps({
  // 字符串类型
  msg: {
    type: String,
    required: true, // 是否必传
    default: '默认值' // 非必传时的默认值
  },
  // 数字类型
  count: {
    type: Number,
    default: 0
  },
  // 对象类型(默认值需用函数返回,避免引用共享)
  user: {
    type: Object,
    default: () => ({
      name: '默认用户',
      age: 18
    }),
    // 自定义校验规则
    validator: (value) => {
      return value.age >= 0; // 校验年龄必须非负
    }
  }
});

// 访问Props(组合式API中可直接通过props.xxx访问)
console.log(props.msg);
</script>
步骤 2:父组件传递数据给子组件

父组件(如 Parent.vue)通过自定义属性传递数据,支持静态值、动态绑定(v-bind / :):

复制代码
<!-- Parent.vue -->
<template>
  <div>
    <!-- 静态传递(仅字符串) -->
    <Child msg="Hello 子组件" />

    <!-- 动态传递(绑定变量/表达式) -->
    <Child 
      :msg="parentMsg" 
      :count="parentCount" 
      :user="parentUser" 
    />
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue';
import Child from './Child.vue';

// 父组件数据
const parentMsg = ref('父组件的动态消息');
const parentCount = ref(100);
const parentUser = reactive({
  name: '张三',
  age: 25
});
</script>

2. 选项式 API 写法(兼容 Vue2 习惯)

复制代码
<!-- Child.vue(选项式API) -->
<template>
  <p>{{ msg }}</p>
</template>

<script>
export default {
  // 声明Props
  props: {
    msg: {
      type: String,
      required: true
    }
  },
  mounted() {
    console.log(this.msg); // 访问Props
  }
};
</script>

3. 注意事项

  • 单向数据流:子组件不能直接修改 Props(修改会触发警告),若需修改,需通过「子传父」通知父组件修改源数据;
  • 默认值规则:对象 / 数组类型的默认值必须用「函数返回」,避免多个组件实例共享同一个引用;
  • Props 校验:类型支持 String/Number/Boolean/Array/Object/Date/Function/Symbol,也支持自定义构造函数;
  • Props 命名 :父组件传递时建议用 kebab-case(如 :user-name="name"),子组件声明时用 camelCase(userName),Vue 会自动转换。

二、子组件向父组件传参(自定义事件)

核心逻辑

子组件通过 emit 触发自定义事件并传递数据,父组件通过 v-on@)监听事件并接收数据,实现子向父的通信。

1. 基础用法(组合式 API / <script setup>

步骤 1:子组件触发自定义事件

子组件通过 defineEmits 声明事件,再通过 emit 触发并传递数据:

复制代码
<!-- Child.vue -->
<template>
  <div>
    <!-- 点击按钮触发事件 -->
    <button @click="handleClick">向父组件传值</button>
    <button @click="handleSendObj">传递对象数据</button>
  </div>
</template>

<script setup>
// 方式1:简洁写法(声明事件名)
const emit = defineEmits(['change', 'send-obj']);

// 方式2:完整写法(校验事件参数,可选)
const emit = defineEmits({
  // 校验change事件的参数(必须是数字)
  change: (value) => {
    return typeof value === 'number';
  },
  sendObj: (obj) => {
    return obj.name && obj.age;
  }
});

// 触发事件并传递数据
const handleClick = () => {
  // emit(事件名, 传递的参数1, 参数2, ...)
  emit('change', 666); 
};

const handleSendObj = () => {
  emit('send-obj', { name: '李四', age: 30 });
};
</script>
步骤 2:父组件监听事件并接收数据

父组件通过 @事件名 监听子组件的自定义事件,通过回调函数接收数据:

复制代码
<!-- Parent.vue -->
<template>
  <div>
    <p>子组件传递的数字:{{ childNum }}</p>
    <p>子组件传递的对象:{{ childObj.name }}</p>
    
    <!-- 监听子组件事件 -->
    <Child 
      @change="handleChildChange" 
      @send-obj="handleChildObj" 
    />
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue';
import Child from './Child.vue';

const childNum = ref(0);
const childObj = reactive({ name: '', age: 0 });

// 接收子组件的数字数据
const handleChildChange = (num) => {
  childNum.value = num;
  console.log('子组件传递的数字:', num); // 输出 666
};

// 接收子组件的对象数据
const handleChildObj = (obj) => {
  Object.assign(childObj, obj);
  console.log('子组件传递的对象:', obj); // 输出 { name: '李四', age: 30 }
};
</script>

2. 选项式 API 写法

复制代码
<!-- Child.vue(选项式API) -->
<template>
  <button @click="handleClick">传值给父组件</button>
</template>

<script>
export default {
  // 声明要触发的事件(可选)
  emits: ['change'],
  methods: {
    handleClick() {
      // 通过this.$emit触发事件
      this.$emit('change', 888);
    }
  }
};
</script>

<!-- Parent.vue(选项式API) -->
<template>
  <Child @change="handleChange" />
</template>

<script>
import Child from './Child.vue';
export default {
  components: { Child },
  data() {
    return { childNum: 0 };
  },
  methods: {
    handleChange(num) {
      this.childNum = num;
    }
  }
};
</script>

3. 注意事项

  • 事件命名 :子组件 emit 时建议用 kebab-case(如 send-obj),父组件监听时保持一致;
  • 多参数传递:若需传递多个参数,可打包成对象(推荐)或数组,避免参数混乱;
  • 事件校验defineEmits 支持校验事件参数,不符合规则时控制台会警告;
  • 避免原生事件冲突 :自定义事件名不要和原生事件(如 clickinput)重名。

三、父子组件双向绑定(v-model)

Vue3 支持自定义组件的 v-model,本质是「Props + 自定义事件」的语法糖,简化双向绑定逻辑。

1. 基础双向绑定

子组件声明 Props 和事件(默认:modelValue + update:modelValue)
复制代码
<!-- Child.vue -->
<template>
  <input 
    type="text" 
    :value="modelValue" 
    @input="$emit('update:modelValue', $event.target.value)"
  />
</template>

<script setup>
// 声明默认的v-model Props
const props = defineProps(['modelValue']);
// 声明对应的更新事件
const emit = defineEmits(['update:modelValue']);
</script>
父组件使用 v-model 绑定
复制代码
<!-- Parent.vue -->
<template>
  <div>
    <p>双向绑定的值:{{ inputValue }}</p>
    <Child v-model="inputValue" />
  </div>
</template>

<script setup>
import { ref } from 'vue';
import Child from './Child.vue';

const inputValue = ref('初始值');
</script>

2. 多个 v-model 绑定

Vue3 支持给组件绑定多个 v-model,只需自定义 Props 和事件名:

复制代码
<!-- Child.vue -->
<template>
  <input 
    type="text" 
    :value="name" 
    @input="$emit('update:name', $event.target.value)"
  />
  <input 
    type="number" 
    :value="age" 
    @input="$emit('update:age', $event.target.value)"
  />
</template>

<script setup>
const props = defineProps(['name', 'age']);
const emit = defineEmits(['update:name', 'update:age']);
</script>

<!-- Parent.vue -->
<template>
  <Child 
    v-model:name="userName" 
    v-model:age="userAge" 
  />
</template>

<script setup>
import { ref } from 'vue';
import Child from './Child.vue';

const userName = ref('张三');
const userAge = ref(25);
</script>

四、父子组件访问实例(非推荐,应急使用)

Vue3 不推荐直接访问组件实例,但特殊场景下可通过 ref + expose 实现:

1. 父组件访问子组件实例

子组件暴露方法 / 数据(defineExpose
复制代码
<!-- Child.vue -->
<script setup>
import { ref } from 'vue';

const childCount = ref(0);
const addCount = () => {
  childCount.value++;
};

// 暴露给父组件的属性/方法(不暴露则父组件无法访问)
defineExpose({
  childCount,
  addCount
});
</script>
父组件通过 ref 访问
复制代码
<!-- Parent.vue -->
<template>
  <Child ref="childRef" />
  <button @click="handleAccessChild">访问子组件</button>
</template>

<script setup>
import { ref } from 'vue';
import Child from './Child.vue';

const childRef = ref(null); // 子组件实例引用

const handleAccessChild = () => {
  // 访问子组件暴露的属性
  console.log(childRef.value.childCount);
  // 调用子组件暴露的方法
  childRef.value.addCount();
};
</script>

2. 子组件访问父组件实例(不推荐)

复制代码
<!-- Child.vue -->
<script setup>
import { getCurrentInstance } from 'vue';

// 获取组件实例(仅应急使用,易耦合)
const instance = getCurrentInstance();
// 访问父组件实例
const parentInstance = instance.parent;
console.log(parentInstance.props); // 父组件Props
console.log(parentInstance.vnode.props); // 父组件传递的属性
</script>

五、核心总结

通信方向 实现方式 核心 API / 语法 适用场景
父 → 子 Props defineProps / props 父组件向子组件传递初始数据
子 → 父 自定义事件 defineEmits / $emit 子组件通知父组件修改数据
双向绑定 v-model(Props + 事件) v-model / update:xxx 父子组件数据双向同步
实例访问 ref + expose defineExpose / getCurrentInstance 应急访问组件方法 / 数据(慎用)

最佳实践

  1. 优先使用「Props + 自定义事件」,遵循单向数据流,降低组件耦合;
  2. 复杂场景(如跨多级组件、非父子组件)建议使用 Pinia / Provide/Inject,而非多层 Props / 事件;
  3. Props 只做数据传递,子组件不要修改 Props,通过事件让父组件修改源数据;
  4. 避免过度使用组件实例访问(ref + expose),易导致代码维护性下降。
相关推荐
看到我请叫我铁锤2 小时前
vue3中THINGJS初始化步骤
前端·javascript·vue.js·3d
q***25212 小时前
SpringMVC 请求参数接收
前端·javascript·算法
q***33372 小时前
Spring Boot项目接收前端参数的11种方式
前端·spring boot·后端
烛阴2 小时前
从`new()`到`.DoSomething()`:一篇讲透C#方法与构造函数的终极指南
前端·c#
还债大湿兄2 小时前
阿里通义千问调用图像大模型生成轮动漫风格 python调用
开发语言·前端·python
谢尔登3 小时前
defineProperty如何弥补数组响应式不足的缺陷
前端·javascript·vue.js
蓝瑟忧伤3 小时前
前端技术新十年:从工程体系到智能化开发的全景演进
前端
Baklib梅梅3 小时前
员工手册:保障运营一致性与提升组织效率的核心载体
前端·ruby on rails·前端框架·ruby
涔溪4 小时前
实现将 Vue2 子应用通过无界(Wujie)微前端框架接入到 Vue3 主应用中(即 Vue3 主应用集成 Vue2 子应用)
vue.js·微前端·wujie