VUE 父组件与子组件交互
1. 概述
1.1 说明
在项目过程中,会有很多重复功能在多个页面中处理,此时则需要把这些重复的功能进行单独拎出,编写公用组件(控件)进行引用。在VUE中,组件是可复用的VUE实例,此时组件中的data必须是一个函数,因为data是一个函数时,每次引用此组件时相当于重新对返回对象进行独立的拷贝(实例化/new),如果data不是一个函数而是一个对象,那么多个引用同一组件时其中一个引用更改数据,其他引用中的数据都会更改。由于业务的不同,组件中的数据交互也会不同。
1.2 父组件向组件传递数据(Prop)
Prop可以在组件上注册一些自定义特性,当一个值传递给一个prop特性的时候,它就变成了那个组件(子组件)实例的一个属性,可以像访问data中的值一样去访问prop中的特性(属性列表)。任何类型的值都可以传给一个prop,prop使得其父子prop之间形成一个单向下行绑定,即数据是在父组件中更改后才绑定到子组件中,父组件每次发生更新,子组件中所有的prop都将会刷新为最新的值。
prop的常见书写方式如:
-
- props: ['name', 'address', 'desc']
- props: { name: String, address: String, desc: String, userInfo: Object }
1.2.1 子组件示例代码
子组件props中需要包含父组件流向的数据信息,以便显示在子组件中(可以直接页面绑定{{}},也可数据接收处理)。
<template> <div class="child-wrap"> <p>姓名:{{name}}</p> <p>性别:{{sex}}</p> <p>年龄:{{age}}</p> </div> </template> <script> export default { props:{ name:{ type:String, default:'' }, sex:{ type:String, default:'' }, age:{ type:Number, default:0 } } } </script> <style scoped> </style>
1.2.2 父组件示例代码
父组件中对子组件传递的数据像使用属性一样的去传递。
<template> <div class="parent-wrap"> <child-component :sex="pSex" :name="pName" :age="pAge"></child-component> </div> </template> <script> import child from './child/child' export default { data() { return { pSex:'男', pName:'张三', pAge:28 } }, components: { "child-component": child }, } </script> <style scoped> </style>
1.3 父组件调用子组件事件
父组件直接访问子组件时,需要使用ref为子组件指定一个引用ID,渲染完成后即为指定子组件的实例,即可操作其内方法。
1.3.1 子组件示例代码
子组件methods中需要包含父组件所调用的子组件方法,可以传递参数值从父组件至子组件,以便来更改或操作子组件中的某些数据。
<template> <div class="child-wrap"> <button @click="confirmClick">子组件按钮</button> </div> </template> <script> export default { methods: { confirmClick(val) { console.log(val) } } } </script> <style scoped> </style>
1.3.2 父组件示例代码
在父组件中,子组件使用ref来注册引用信息,引用信息将会注册在父组件的$refs对象上。
注意:ref被用来给元素或子组件注册引用信息,如果是dom元素,引用只想的就是dom元素,如果是组件,引用就指向组件实例。
ref注册时间:ref本身是作为渲染结果被创建的,在初始渲染的时候不能够进行访问它。
<template> <div class="parent-wrap"> <child-component ref="child"></child-component> <button @click="parentClick" class="btnClass">父组件按钮</button> </div> </template> <script> import child from './child/child' export default { data() { return {} }, components: { "child-component": child }, methods: { parentClick() { this.$refs.child.confirmClick("父组件调用"); } } } </script> <style scoped> </style>
1.4 子组件触发父组件事件
在子组件里用$emit向父组件触发一个事件,父组件监听这个事件。
1.4.1 子组件示例代码
子组件methods中去注册能够调用父组件方法的入口。
<template> <div class="child-wrap"> <button @click="confirmClick">子组件按钮</button> </div> </template> <script> export default { methods: { confirmClick() { this.$emit('child-click','点击了子组件按钮') } } } </script> <style scoped> </style>
1.4.2 父组件示例代码
父组件的子组件中使用事件进行监听所需调用的方法,其中@后跟随的是子组件中注册的自定义事件名,后边所调用的方法为父组件中所定义的方法名。
<template> <div class="parent-wrap"> <child-component @child-click="childClick"></child-component> <p>{{strChild}}</p> </div> </template> <script> import child from './child/child' export default { data() { return { strChild:'' } }, components: { "child-component": child }, methods: { childClick(val) { this.$data.strChild=val; } } } </script> <style scoped> </style>
注意:也可直接props传递方法进行子组件调用父组件方法。
2. 数据交互
在实际项目应用中,很多时候需要实时的获取到子组件中的数据信息,即子组件数据更改父组件数据。此时就需要监控子组件数据是否更改(watch),而子组件则需要进行计算其数据集(computed)
2.1 代码示例
2.1.1 父组件示例代码
子组件每次调用时都需要是重新实例化的(每次调用子组件才会是单独的组件),故可以使用v-if来进行处理
<template> <div> <Button type='primary' @click='addPerson()'>新增</Button> <Button type='primary' @click='editPerson()'>编辑</Button> <Modal v-model='childModalVisible' title="新增人员" width='800' @on-cancel='cancleModal' closable> <child-update v-if="childModalVisible" ref="childUpdate" :data='objChild' /> <div slot='footer'> <Button @click='cancleModal'>取消</Button> <Button @click='submitModal' type='primary'>确定</Button> </div> </Modal> <Row style="margin-top: 20px; background: #f5f5f5;font-size: 40px;font-weight: 600;"> <Col span="6">姓名:{{objChild.name}}</Col> <Col span="6">性别:{{objChild.sex}}</Col> <Col span="6">电话:{{objChild.phoneNo}}</Col> <Col span="6">地址:{{objChild.address}}</Col> </Row> </div> </template> <script> import ChildUpdate from '../components/updateChild' export default { name: "updateParent.vue", components: { ChildUpdate, }, data(){ return { childModalVisible:false, objChild:{} } }, methods:{ addPerson(){ this.objChild={ name: '', sex: 0, phoneNo: '', address:'' }; this.childModalVisible=true }, editPerson(){ this.objChild={ name: '张三', sex: 0, phoneNo: '15136466666', address:'浙江省杭州市' } this.childModalVisible=true }, cancleModal(){ this.childModalVisible=false }, submitModal(){ if(this.$refs.childUpdate.validateForm('childPersonForm')){ let bSubmit=true if (this.objChild.phoneNo) { const reg = /^[1][3,4,5,7,8][0-9]{9}$/ if (!reg.test(this.objChild.phoneNo)) { bSubmit=false } } if(bSubmit){ this.childModalVisible=false } else { this.$Message.error('请输入正确的电话号码!') } } else { this.$Message.error('请输入正确的人员信息!') } } } } </script> <style scoped> </style>
2.1.2 子组件示例代码
通过监控子组件数据更新去更新父组件数据信息,使用watch;
通过计算属性去判断父组件数据更新赋值于子组件数据,使用 computed
<template> <div> <Form ref='childPersonForm' :model='childPersonForm' :rules='formRules' :label-width='90'> <Row> <Col span='12'> <FormItem label='姓名' prop='name'> <Input v-model='childPersonForm.name' :maxlength='20' type='text' placeholder='请输入'></Input> </FormItem> </Col> <Col span='12'> <FormItem label='性别' prop='sex'> <Select v-model='childPersonForm.sex' clearable> <Option v-for='item in personSexOption' :value='item.value' :label='item.name' :key='item.value'>{{item.name}}</Option> </Select> </FormItem> </Col> </Row> <Row> <Col span='12'> <FormItem label='电话' prop='phoneNo'> <Input v-model='childPersonForm.phoneNo' :maxlength='11' type='text' @on-blur='telNoValidate' placeholder='请输入'></Input> </FormItem> </Col> <Col span='12'> <FormItem label='住址' prop='address'> <Input v-model='childPersonForm.address' :maxlength='20' type='text' placeholder='请输入'></Input> </FormItem> </Col> </Row> </Form> </div> </template> <script> export default { props: { data: { type: Object, default() { return { name: '', sex: 0, phoneNo: '', address:'' } }, }, }, data() { // eslint-disable-next-line const nameRules = (rule, value, callback) => { const reg = /[0-9]/ const regEn = /[`~!@#$%^&*()_+<>?:"{},\\.\/;'[\]]/im const regCn = /[·!#¥(——):;“”‘、,|《。》?、【】[\]]/im if (value === '') { callback(new Error('姓名不能为空')) } else if (regEn.test(value) || regCn.test(value)) { callback(new Error('姓名不能包含特殊字符')) } else if (reg.test(value)) { callback(new Error('姓名不能包含数字')) } else { callback() } } return { formRules: { name: [{ required: true, validator: nameRules, trigger: 'blur' }], }, } }, computed: { personSexOption() { return [{value:0,name:'男'},{value:1,name:'女'}] }, childPersonForm() { console.log(this.data) return this.data }, }, watch: { childPersonForm: { handler(oldVal, newVal) { this.data.name = newVal.name this.data.sex = newVal.sex this.data.phoneNo = newVal.phoneNo this.data.address = newVal.address }, deep: true, }, }, methods: { telNoValidate() { if (this.childPersonForm.phoneNo) { const reg = /^[1][3,4,5,7,8][0-9]{9}$/ if (!reg.test(this.childPersonForm.phoneNo)) { this.$Message.error('请输入正确的电话号码!') } } }, /** * 表单验证 */ validateForm(name) { let bReturn = false this.$refs[name].validate(valid => { if (valid) { bReturn = true } else { bReturn = false } }) return bReturn }, }, } </script> <style scoped> </style>
3. 父子组件生命周期钩子执行顺序
- 加载渲染过程
- 父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
- 子组件更新过程
- 父beforeUpdate->子beforeUpdate->子updated->父updated
- 父组件更新过程
- 父beforeUpdate->父updated
- 销毁过程
- 父beforeDestroy->子beforeDestroy->子destroyed->父destroyed