子传父的核心应用场景(vue3) - 教程
我们常见的组件通信的方式是prop和emit,下面我们来探讨一下什么场景下使用子传父。
子传父的核心场景是:子组件产生了“需要父组件响应”的事件或数据,但子组件自身无法(或不应该)处理,必须交给父组件决策/存储/分发。
父传子是“数据下行”(父给子展示用),子传父是“事件/数据上行”(子告诉父“我发生了什么”,让父处理)。以下是最常见的场景,带具体例子和简单实现思路:
一、最核心场景:子组件的用户交互,需要父组件处理逻辑
子组件是“交互载体”(比如按钮、输入框、表单),用户操作子组件后,子组件本身不知道要做什么业务逻辑,必须通知父组件执行。
1. 按钮/开关的“触发事件”
- 场景:子组件是一个自定义按钮(比如“提交按钮”“删除按钮”),用户点击后,触发了子组件的事件:如提交表单、删除操作等,但是子组件不知道要提交什么、删除什么,需要父组件执行提交API、删除数据等逻辑。
- 例子:
- 子组件(
MyButton):只负责渲染按钮,点击时触发父组件传来的回调。 - 父组件:接收点击事件,执行“提交表单”的逻辑。
- 子组件(
- 简单代码:
<!-- 子组件 MyButton.vue -->
<template>
<button @click="handleClick"> {{ btnText }} </button>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
btnText: {
type: String,
required: true
}
});
const emit = defineEmits(['click']);
const handleClick = () => {
emit('click');
};
</script>
<!-- 父组件 Parent.vue -->
<template>
<MyButton btnText="提交" @click="submitForm" />
</template>
<script setup>
import MyButton from './MyButton.vue';
const submitForm = () => {
// 调用API提交数据、跳转页面等操作
};
</script>
2. 输入框/选择器的“数据回传”
- 场景:子组件是一个自定义输入框(比如“搜索输入框”“日期选择器”),用户输入/选择后,子组件需要把输入的值传给父组件,父组件可能用这个值做搜索、筛选、存储等。
- 例子:
- 子组件(
MyInput):负责输入框渲染,输入时把值传给父组件。 - 父组件:接收输入值,执行“搜索数据”的逻辑。
- 子组件(
- 简单代码:
<!-- 子组件 MyInput.vue -->
<template>
<input
type="text"
:value="modelValue"
@input="handleInput"
placeholder="请输入搜索关键词"
/>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
modelValue: {
type: String,
default: ''
}
});
const emit = defineEmits(['update:modelValue']);
const handleInput = (e) => {
const inputValue = e.target.value;
emit('update:modelValue', inputValue);
};
</script>
<!-- 父组件 Parent.vue -->
<template>
<MyInput v-model="searchKey" />
<button @click="searchData">搜索</button>
<p>搜索关键词: {{ searchKey }}</p>
</template>
<script setup>
import { ref } from 'vue';
import MyInput from './MyInput.vue';
const searchKey = ref('');
const searchData = () => {
// 调用API搜索数据...
};
</script>
3. 表单提交的“数据汇总”
- 场景:子组件是一个完整表单(比如“用户注册表单”“商品编辑表单”),用户填写完成后点击提交,子组件收集所有表单字段的值,传给父组件,父组件处理API提交、数据校验等。
- 例子:
- 子组件(
UserForm):收集姓名、年龄等字段,提交时把表单数据传给父。 - 父组件:接收表单数据,执行“注册用户”的API请求。
- 子组件(
二、子组件状态变化,需要父组件同步
子组件自身管理了一些状态(比如折叠/展开、选中状态),但父组件需要知道这个状态(比如父组件的标题要根据子组件的折叠状态变化,或父组件要根据子组件的选中状态更新其他UI)。
1. 折叠面板/抽屉的“状态同步”
- 场景:子组件是一个折叠面板(比如“筛选条件面板”),用户点击展开/收起时,子组件需要把“是否展开”的状态传给父组件,父组件可能要显示/隐藏其他元素(比如“展开时显示重置按钮,收起时隐藏”)。
- 简单代码:
<!-- 子组件 CollapsePanel.vue -->
<template>
<div class="panel">
<div class="header" @click="toggleCollapse">
{{ title }}
<span>{{ isCollapsed ? '展开' : '收起' }}</span>
</div>
<div class="content" v-show="!isCollapsed">
面板内容...
</div>
</div>
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue';
const props = defineProps({
title: {
type: String,
required: true
},
collapsed: {
type: Boolean,
required: true
}
});
const emit = defineEmits(['update:collapsed']);
const isCollapsed = ref(props.collapsed);
// 监听 props 变化,同步到内部状态
watch(() => props.collapsed, (newVal) => {
isCollapsed.value = newVal;
});
const toggleCollapse = () => {
isCollapsed.value = !isCollapsed.value;
emit('update:collapsed', isCollapsed.value);
};
</script>
<!-- 父组件 Parent.vue -->
<template>
<CollapsePanel title="筛选条件" v-model:collapsed="isPanelCollapsed" />
<button v-show="!isPanelCollapsed" @click="resetFilter">重置筛选</button>
</template>
<script setup>
import { ref } from 'vue';
import CollapsePanel from './CollapsePanel.vue';
const isPanelCollapsed = ref(true);
const resetFilter = () => {
console.log('父组件:重置筛选条件');
};
</script>
2. 选项卡/分页的“切换事件”
- 场景:子组件是一个分页组件(比如“商品列表分页”),用户点击“下一页”时,子组件需要把“当前页码”传给父组件,父组件根据页码请求对应页的数据。
- 例子:
- 子组件(
Pagination):管理页码、每页条数,页码变化时通知父。 - 父组件:接收页码,请求对应页的商品数据。
- 子组件(
三、子组件需要“反向控制”父组件的UI/数据
子组件的某个操作,需要父组件改变自身的状态(比如父组件的弹窗显示/隐藏、父组件的数据源更新)。
1. 弹窗的“关闭事件”
- 场景:子组件是一个弹窗(比如“提示弹窗”“确认弹窗”),用户点击“关闭”或“取消”时,子组件需要通知父组件“我要关闭了”,父组件控制弹窗的
v-show或v-if状态。 - 简单代码:
<!-- 子组件 MyModal.vue -->
<template>
<div class="modal" v-if="modelValue">
<div class="modal-content">
<h3>弹窗标题</h3>
<p>弹窗内容...</p>
<button @click="handleClose">关闭</button>
</div>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
modelValue: {
type: Boolean,
required: true
}
});
const emit = defineEmits(['update:modelValue', 'close']);
const handleClose = () => {
emit('update:modelValue', false);
emit('close');
};
</script>
<!-- 父组件 Parent.vue -->
<template>
<button @click="showModal = true">打开弹窗</button>
<!--
使用 v-model 绑定弹窗的显示状态。
同时监听 'close' 事件,执行额外的回调。
-->
<MyModal v-model="showModal" @close="onModalClose" />
</template>
<script setup>
import { ref } from 'vue';
import MyModal from './MyModal.vue';
const showModal = ref(false);
// 弹窗关闭时的回调
const onModalClose = () => {
};
</script>
2. 子组件触发父组件的数据源更新
- 场景:子组件是一个“新增用户”组件,用户填写完信息提交后,子组件把新增的用户数据传给父组件,父组件把这个数据添加到自己的“用户列表”数据源中,实现列表实时更新。
总结:子传父的核心逻辑
子组件是“执行者”(负责UI渲染和用户交互),父组件是“决策者”(负责业务逻辑、数据存储、全局状态管理)。当子组件发生了“自己处理不了”的事件或数据变化时,就需要通过$emit(Vue)、props回调(React)等方式“上报”给父组件,让父组件处理。
简单记:父传子是“给数据”,子传父是“报事件”。
浙公网安备 33010602011771号