Vue基础知识-脚手架开发-子传父(props回调函数实现和自定义事件实现)
一、项目概况与结构
本文通过「App 父组件」+「School 子组件」+「Student 子组件」演示两种子传父方式:
- School 组件:用「props 回调函数」传递学校名称(北京大学)给 父App;
- Student 组件:用「自定义事件」传递学生名称(张三)给 父App,同时演示事件解绑;
- App 组件:作为父组件,接收并展示子组件传递的数据。
项目结构(Vue 2 标准结构):
src/
├─ components/ # 子组件目录
│ ├─ School.vue # props回调演示
│ └─ Student.vue # 自定义事件演示
├─ App.vue # 父组件(整合子组件)
└─ main.js # 入口文件(初始化Vue)
二、完整代码实现
1. 入口文件:main.js
import Vue from 'vue'import App from './App.vue'Vue.config.productionTip = falsenew Vue({render: h => h(App),
}).$mount('#app')
2. 子组件 1:School.vue(props 回调函数实现子传父)
核心逻辑:父组件传递一个回调函数给子组件(通过 props),子组件调用该函数并传入数据,父组件在回调中接收数据。
<template><div class="demo"><h2>学校名称:{{name}}</h2><input type="button" value="传递学校名(props实现)" @click="sendSchoolName()" /></div>
</template>
<script>export default {name:'School',data(){return {name:'北京大学'}},props:['getSchoolName'],methods:{sendSchoolName(){this.getSchoolName(this.name)}}}
</script><style scoped>.demo{background-color: red;padding:10px;}
</style>
3. 子组件 2:Student.vue(自定义事件实现子传父)
核心逻辑:父组件给子组件绑定自定义事件,子组件通过this.$emit('事件名', 数据)
触发事件,父组件在事件回调中接收数据;同时演示事件解绑(this.$off
)。
<template><div class="demo"><h2>学生名称:{{name}}</h2><input type="button" value="传递学生名(自定义事件实现)" @click="sendStudentlName" /><input type="button" value="解绑自定义事件" @click="unbind" /></div>
</template>
<script>export default { name:'Student',data(){return {name:'张三'}},methods:{sendStudentlName(){//触发自定义事件@getStudentName,并传入参数this.$emit('getStudentName',this.name)},unbind(){//解绑一个this.$off('getStudentName')//解绑多个this.$off(['getStudentName','xx'])//解绑所有this.$off()}}}
</script><style scoped>.demo{background-color: orange;padding: 10px;margin-top: 30px;}
</style>
4. 父组件:App.vue(接收子组件数据)
作为父组件,分别用两种方式接收子组件数据,并展示结果;同时演示自定义事件的 “手动绑定” 和@click.native
的用法。
<template><div class="app"><h2>学校名称:{{schoolName}}</h2><h2>学生名称:{{studentlName}}</h2><!--使用props实现子传父:父传回调函数getSchoolName给子调用。本质调父的getSchoolName(子写props接收并调用)--><School :getSchoolName="getSchoolName"/><!--使用自定义事件实现子传父:父给子绑定自定义事件并由子来触发。(子写this.$emit('',args[])触发自定义事件)--><!-- 第一种实现:直接绑定(@用于组件上)。当Student实例触发@getStudentName事件,getStudentName调用。要求触发一次:@getStudentName加上.once即可<Student @getStudentName="getStudentName"/>--><!-- 第二种实现:手动绑定。--><Student ref="student"/><!-- 注:<Student @click.native="nativeClick"/>@click.native表示单击事件(最终绑定在Student组件里template的根div)。不加native会被当作自定义事件。 --></div>
</template><script>import School from './components/School' import Student from './components/Student'; export default {name:'App',data() {return {schoolName:'',studentlName:''}},components:{School,Student},methods:{getSchoolName(schoolName){this.schoolName = schoolName},getStudentName(studentlName){this.studentlName = studentlName},nativeClick(){alert('原生事件')}},mounted(){/* 第二种实现,手动绑定:当Student实例触发@getStudentName事件,getStudentName调用。要求触发一次:this.$refs.student.$once('getSstudentName',this.getStudentName) */this.$refs.student.$on('getStudentName',this.getStudentName)/* 写生匿名函数注意this指向问题:下面的this指向App组件,若不写箭头函数则this指向Student组件导致错误。this.$refs.student.$on('getStudentName',(studentlName)=>{this.studentlName = studentlName}) */}}
</script><style>.app{background-color: gray;padding: 10px;}</style>
三、核心知识点深度解析
1. 方式一:props 回调函数实现子传父
原理
父组件定义一个 “接收数据的回调函数”,通过props
将该函数传递给子组件;子组件调用这个回调函数时,将内部数据作为参数传入,父组件在回调中拿到数据并更新自身状态。
关键步骤(3 步)
- 父组件定义回调函数:如
getSchoolName(schoolName) { this.schoolName = schoolName }
; - 父传子:通过 props 传递回调:
<School :getSchoolName="getSchoolName" />
; - 子传父:调用回调函数传数据:子组件
props: ['getSchoolName']
接收,this.getSchoolName(this.name)
触发传递。
优缺点
- 优点:逻辑简单,代码量少,适合 “一次性传递” 或 “简单场景”;
- 缺点:不支持事件解绑,若子组件多次触发,父组件无法主动停止接收;且 props 本质是 “数据传递”,用它传函数不符合 “props 仅传数据” 的设计初衷(语义化稍差)。
2. 方式二:自定义事件实现子传父
原理
Vue 组件实例自带事件系统($on
绑定、$emit
触发、$off
解绑),父组件给子组件绑定一个 “自定义事件”,子组件通过$emit
触发该事件并传递数据,父组件在事件回调中接收数据;可通过$off
主动解绑事件,避免内存泄漏。
两种绑定方式(灵活选择)
绑定方式 | 语法示例 | 适用场景 |
---|---|---|
模板直接绑定 | <Student @getStudentName="getStudentName" /> | 简单场景,推荐优先使用 |
手动绑定($refs) | this.$refs.studentRef.$on('事件名', 回调) | 需动态绑定 / 解绑(如条件绑定) |
事件解绑(3 种场景)
- 解绑单个事件:
this.$off('事件名')
(如 Student 组件的this.$off('getStudentName')
); - 解绑多个事件:
this.$off(['事件1', '事件2'])
; - 解绑所有事件:
this.$off()
(谨慎使用,会解绑组件所有自定义事件)。
优缺点
- 优点:语义化清晰(专门用于 “事件通信”),支持绑定 / 解绑,适合 “多次触发” 或 “需要动态控制” 的场景;
- 缺点:代码量略多,需理解 Vue 组件的事件系统(
$on
/$emit
/$off
)。
3. 补充:@click.native 的作用
在组件标签上用@click
时,Vue 默认将其视为 “自定义事件”(需子组件$emit('click')
才触发);若想绑定组件根 DOM 的原生 click 事件,需加native
修饰符:
- 错误用法:
<Student @click="handleClick" />
(子组件需$emit('click')
才触发); - 正确用法:
<Student @click.native="handleClick" />
(直接绑定子组件根 DOM 的原生 click 事件,无需子组件处理)。
原理:native
修饰符会 “穿透组件”,将事件绑定到子组件的根 DOM 元素上,本质是监听原生 DOM 事件(如click
、input
)。
四、两种子传父方式对比总结
对比维度 | props 回调函数 | 自定义事件 |
---|---|---|
实现原理 | 父传回调函数,子调用传参 | 父绑定事件,子 $emit 触发传参 |
绑定方式 | 仅模板绑定(:props="回调") | 模板绑定(@事件名)/ 手动绑定(refs.on) |
事件解绑 | ❌ 不支持 | ✅ 支持($off) |
触发次数控制 | ❌ 无(子组件可无限次调用) | ✅ 支持单次触发($once) |
语义化 | 稍差(props 本应传数据,而非函数) | 优(专门用于事件通信) |
适用场景 | 简单场景、一次性传递(如初始化数据) | 复杂场景、多次触发、需解绑(如交互操作) |
代码复杂度 | 低(3 步完成) | 中(需理解事件 API) |
五、注意事项
- 单向数据流:无论哪种方式,都遵循 Vue “单向数据流” 原则 —— 子组件仅传递数据,不直接修改父组件数据;父组件接收数据后自行更新,确保数据流向可追溯。
- 事件名大小写:自定义事件名推荐用 “kebab-case”(短横线命名),如
get-student-name
,避免用 “camelCase”(驼峰命名)(Vue 模板中不区分大小写,可能导致事件名匹配失败)。 - 手动绑定时机:通过
$refs
手动绑定事件时,需在mounted
钩子中执行(确保子组件已挂载,$refs
能获取到组件实例),避免在created
中执行(此时子组件未渲染,$refs
为undefined
)。 - 内存泄漏:若子组件是 “动态渲染”(如
v-if
控制显示隐藏),建议在组件卸载前用$off
解绑事件,避免事件残留导致内存泄漏。
六、总结
Vue 子传父的两种方式各有适用场景:
- 简单场景(如一次性传递初始化数据):优先用props 回调函数,代码简洁;
- 复杂场景(如多次交互、需动态解绑):优先用自定义事件,语义化清晰且灵活。
无论选择哪种方式,核心都是遵循 “单向数据流”,让父组件统一管理数据,确保项目后期可维护性。