一、前言
前端开发中组件的封装是必不可少的,高效的组件数据交互能够减少重复代码的生成与用户体验的提升。本文将分以下三种情况去分析组件间的数据交互方式:
1、父子组件间的数据交互(props <---> $emit、$refs <---> $parent);
2、兄弟组件间的数据交互(this.$parent.$refs);
3、非亲组件(两个组件之间没有任何关联)间的数据交互(window['methodName'] = (event, params) => { },window.parent.methodName(event))。
二、父子组件数据交互
<-- 父组件 Person.vue -->
<template>
<div>
<el-form ref="personRef" :model="person">
<el-form-item prop="id">
<el-input v-model="person.id" clearable></el-input>
</el-form-item>
<el-form-item prop="name">
<el-input v-model="person.name" clearable></el-input>
</el-form-item>
<el-form-item prop="age">
<el-input v-model="person.age" clearable></el-input>
</el-form-item>
<el-form-item>
<el-input v-model="approveInfo" disabled></el-input>
</el-form-item>
</el-form>
<el-button @click="this.showDialog = true">提交</el-button>
<person-approve-dialog
ref="personApproveRef"
v-if="showDialog"
:personInfo="person"
@getApproveComment="getApproveComment"
>
</person-approve-dialog>
</div>
</template>
<script>
import PersonApproveDialog from '@/components/PersonApproveDialog.vue'
export default {
name: 'Person',
components: { PersonApproveDialog },
data() {
return {
showDialog: false,
approveInfo: '',
person: {
id: '',
name: '',
age: ''
}
}
},
methods: {
getApproveComment(value) {
this.approveInfo = value;
if (this.$refs['personApproveRef'].approveComment.lenght > 100) {
this.$message(message: '审批内容超长', type: 'error')
}
this.$nextTick(() => {
this.showDialog = false
})
}
}
}
</script>
<-- 子组件 PersonApproveDialog.vue -->
<template>
<el-dialog :visible.sync="visible">
<el-form ref="personApproveRef">
<el-form-item>
<el-input v-model="personInfo.id" disabled></el-input>
</el-form-item>
<el-form-item>
<el-input v-model="personInfo.name" disabled></el-input>
</el-form-item>
<el-form-item>
<el-input v-model="personInfo.age" disabled></el-input>
</el-form-item>
<el-form-item prop="approveComment">
<el-input v-model="approveComment" clearable></el-input>
</el-form-item>
</el-form>
<el-button @click="checkValid()">核验</el-button>
<el-button @click="close()">关闭</el-button>
</el-dialog>
</template>
<script>
export default {
name: 'PersonApproveDialog',
props: { personInfo: Object },
data() {
return {
visible: true,
approveComment: ''
}
},
methods:{
checkValid() {
if (this.$parent.person.id === '') {
this.$message(message: '人员 id 必填', type: 'error')
}
},
close() {
this.visible = false,
this.$emit('getApproveComment', this.approveComment)
}
}
}
</script>
分析:
(1)子组件获取父组件的数据:
方法一:通过在子组件中添加 props 属性来获取父组件的数据,如上图代码 :personInfo="person",父组件的 person 对象赋值给子组件的 personInfo 属性,子组件从 personInfo 中即可拿到父组件的人员信息数据。
方法二:通过 this.$parent 主动去获取父组件的数据信息,this.$parent 表示获取当前子组件的父组件信息(包含父组件的变量和方法),案例中的 this.$parent.person.id 取的就是父组件中的变量(人员 id 值)。
(2)父组件获取子组件的数据:
方法一:通过 $emit 来传递数据,图中 this.$emit('getApproveComment', this.approveComment) 表示触发 getApproveComment 事件,并将子组件的 approveComment 栏位值传过去,父组件根据这个事件去执行自己的方法 getApproveComment(value),其中 value 就是子组件传过来的approveComment 的值。
方法二:通过 this.$refs 主动去获取子组件的数据信息,图中this.$refs['personApproveRef'].approveComment 即可获取到 personApproveRef 对应的子组件的审批内容。
三、兄弟组件数据交互
<-- 父组件 Address.vue -->
<template>
<div>
<el-form>
<el-form-item prop="address">
<el-input v-model="address" clearable></el-input>
</el-form-item>
</el-form>
<province-dialog ref="provinceRef"></province-dialog>
<city-dialog ref="cityRef"></city-dialog>
</div>
</template>
<script>
import ProvinceDialog from '@/components/ProvinceDialog.vue'
import CityDialog from '@/components/CityDialog.vue'
export default {
name: 'Address',
components: { ProvinceDialog, CityDialog },
data() {
return {
address: ''
}
}
}
</script>
<-- 子组件 ProvinceDialog.vue -->
<template>
<el-dialog :visible.sync="visible">
<el-form>
<el-form-item prop="provinceName">
<el-input
v-model="provinceName"
clearable
@change="changeProvince">
</el-input>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script>
export default {
name: 'ProvinceDialog',
data() {
return {
visible: true,
provinceName: ''
}
},
methods: {
changeProvince(value) {
if (this.$parent.$refs['cityRef'].cityName === '') {
this.$message(message: '城市名为空,无法获取详细地址', type: 'error')
} else {
this.$parent.address = value + this.$parent.$refs['cityRef'].cityName
}
}
}
}
</script>
<-- 子组件 CityDialog.vue -->
<template>
<el-dialog :visible.sync="visible">
<el-form>
<el-form-item prop="cityName">
<el-input v-model="cityName" @change="changeCity"></el-input>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script>
export default {
name: 'CityDialog',
data() {
return {
visible: true,
cityName: ''
}
},
methods: {
changeCity(value) {
if (this.$parent.$refs['provinceRef'].provinceName === '') {
this.$message(message: '省份为空,无法获取详细地址', type: 'error')
} else {
this.$parent.address = this.$parent.$refs['provinceRef'].provinceName
+ value
}
}
}
}
</script>
分析:
一个父组件引用了多个子组件,那么这多个子组件间就称之为兄弟组件,它们之间的数据交互主要是通过父组件(this.$parent)来进行, 父组件在其中充当着数据传递的桥梁作用。如代码所示,一个子组件存放省份信息,另一个子组件存放城市信息,只要省份和城市任何一个改变了,那么由二者拼接而成的地址也就会变化,this.$parent.$refs['provinceRef'].provinceName 就是先通过 this.$parent 获取父组件信息,然后通过 $refs['provinceRef'] 获取父组件的子组件信息,从而拿到兄弟组件 ProvinceDialog.vue 的省份名称,同理 this.$parent.$refs['cityRef'].cityName 也是先通过 this.$parent 获取父组件信息,然后通过 $refs['cityRef'] 获取父组件的子组件信息,从而拿到兄弟组件 CityDialog.vue 的城市名称,进而拼接成详细的地址信息。
四、非亲组件数据交互
<-- 组件 ProvinceDialog.vue -->
<template>
<el-dialog :visible.sync="visible">
<el-form>
<el-form-item prop="provinceName">
<el-input
v-model="provinceName"
clearable
@change="changeProvince">
</el-input>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script>
export default {
name: 'ProvinceDialog',
data() {
return {
visible: true,
provinceName: ''
}
},
mounted() {
let _this = this;
window['getProvinceName'] = (event) => { return _this.provinceName }
},
methods: {
changeProvince(value) {
if (window.parent.getCityName && window.parent.getCityName(event)
=== '') {
this.$message(message: '城市为空,无法获取详细地址', type: 'error')
}
}
}
</script>
<-- 组件 CityDialog.vue -->
<template>
<el-dialog :visible.sync="visible">
<el-form>
<el-form-item prop="cityName">
<el-input v-model="cityName" clearable @change="changeCity"></el-input>
</el-form-item>
</el-form>
</el-dialog>
</template>
<script>
export default {
name: 'CityDialog',
data() {
return {
visible: true,
cityName: ''
}
},
mounted() {
let _this = this;
window['getCityName'] = (event) => { return _this.cityName }
},
methods: {
changeCity(value) {
if (window.parent.getProvinceName && window.parent.getProvinceName(event)
=== '') {
this.$message(message: '省份为空,无法获取详细地址', type: 'error')
}
}
}
}
</script>
分析:
如果两个组件未在同一个父组件中引用,那么称这两个组件为非亲组件,即它们没有任何直接的联系,它们之间的数据交互可以先定义一个获取数据的方法:window['methodName'] ,然后在需要获取数据的组件中使用该方法:window.parent.methodName(),这种方式存在一个弊端就是定义数据获取方法 methodName 所在的组件需要使用过(至少进入生命周期 mounted 一次),否则 window.parent.methodName 为 undefined,所以需要注意定义数据获取方法的位置,可以在app.vue 中定义,也可以在 main.js 中,根据自身所需去写就好。除了这种方法,其实还有一种使用 inject 和 provide 的方式,此类方式本文不做赘述。