在 Vue 开发中,组件化是提升代码复用性和可维护性的重要手段。通过组件化,可以将常用的功能封装为独立的组件,并在需要的地方复用。本文将介绍如何在 Vue 中实现父组件与子组件之间的数据传递,以及子组件如何调用父组件的方法。
一、组件封装的基本思路
在 Vue 中,通常会将一些可复用的逻辑、UI 封装成单独的组件。比如一个表单,如果需要在多个地方使用,最好的做法是将其提取为一个子组件,而不是在多个地方重复编写相同的代码。
项目场景
前端有一个页面,需要要 《新增》 与 《详情》 展示一个内容一样的表单窗口。为了代码复用,我们将表单窗口提取为一个子组件,并且嵌入到《新增》 与 《详情》的代码中复用。我们需要在父组件中控制这个表单的显示与隐藏,并在表单提交后关闭窗口并刷新父组件的数据。
二、父组件向子组件传递数据与子组件调用父组件方法
在 Vue 中,父组件通过 props
向子组件传递数据。props
是一种单向数据流,从父组件流向子组件。子组件在执行保存表单后调用父组件的方法实现,关闭窗口与刷新父组件数据列表。我们来看以下实现代码:
1. 父组件
<!-- ParentComponent.vue -->
<template>
<div>
<h1>Parent Component</h1>
<button @click="openForm">Open Form</button>
<!-- 新增窗口 -->
<Drawer title="新增车辆" :mask-closable=false
:closable=true v-model="show_edit_win" width="900"
style="position: relative">
<!-- 使用 props 向子组件传递数据 -->
<edit-form @close-form="handleCloseForm" @refresh-data="refreshData" :brand-data="brandList" ></edit-form>
</Drawer>
<Drawer title="车辆详情" :mask-closable=false
:closable=true v-model="show_detail_win" width="1200"
style="position: relative">
<Tabs value="sub_win_tab" style="height: 100%;">
<TabPane label="车辆信息" name="1">
<edit-form :form-data="entity" @refresh-data="refreshData" :brand-data="brandList" ></edit-form>
</TabPane>
<TabPane label="车辆使用记录" name="2">
</TabPane>
<TabPane label="车辆维修记录" name="3">
</TabPane>
</Tabs>
</Drawer>
</div>
</template>
<script>
import editForm from "./edit_form";
export default {
components: {
editForm
},
data() {
return {
show_detail_win: false, // 控制表单是否显示
brandList:[] //车辆品牌
};
},
methods: {
openForm() {
this.show_detail_win = true;
},
handleCloseForm() {
this.show_edit_win=false;
},
refreshData() {
//业务处理,一般是重新调用拉列表数据
},
}
};
</script>
2. 子组件
<!-- edit_form.vue -->
<template>
<div>
<Form :label-width="90" :model="entity" ref="entity_edit" :rules="ruleValidate">
<div><h4>车辆信息</h4></div>
<Row :gutter="24">
<Col span="8">
<FormItem label="车辆号码:" prop="code">
<Input v-model="entity.code" > </Input>
</FormItem>
</Col>
<Col span="8">
<FormItem label="车辆VIN:" prop="vinCode">
<Input v-model="entity.vinCode" > </Input>
</FormItem>
</Col>
<Col span="8">
<FormItem label="车辆品牌:">
<Select placeholder="车辆品牌" v-model="entity.brand" clearable>
<Option v-for="item in brandList" :value="item.brand" :key="item.brand">{{item.brand}}</Option>
</Select>
</FormItem>
</Col>
</Row>
<Row :gutter="24">
<Col span="8">
<FormItem label="车辆系列:" prop="cardType">
<Input v-model="entity.series" > </Input>
</FormItem>
</Col>
<Col span="8">
<FormItem label="车辆型号:" prop="cardNo">
<Input v-model="entity.model" > </Input>
</FormItem>
</Col>
<Col span="8">
<FormItem label="车辆类型:" prop="cardType">
<Select v-model="entity.type" placeholder="请选择车辆类型" clearable>
<Option v-for="item in typeList" :key="item.value" :value="item.value">{{item.label}}</Option>
</Select>
</FormItem>
</Col>
</Row>
<Row :gutter="24">
<Col span="24">
<FormItem label="备注" prop="remark">
<Input v-model="entity.remark" type="textarea" :autosize="{minRows: 5,maxRows: 8}"
style="width: 800px;"
placeholder="请输入备注信息..."> </Input>
</FormItem>
</Col>
</Row>
</Form>
<div style="text-align: center;padding-bottom: 10px;">
<Button type="primary" @click="doSave">保存</Button>
</div>
</div>
</template>
<script>
export default {
name: "edit_form",
props: {
formData: { //父组件在修改时传入的原对象数据,传入的不是引用而是副本。
type: Object,
required: false
},
brandData: { //父组件传入的品牌数据
type: Array,
required: true
}
},
data() {
return {
brandList:[...this.brandData],//品牌列表
typeList:[
{value:1,label:'公交车'},
{value:2,label:'私家车'},
],
updateUrl: '/backend/resources/vehicle/edit',
saveUrl: '/backend/resources/vehicle/save',
entity:{...this.formData},
ruleValidate: {
code: [ {required: true, message: '不能为空', trigger: 'blur'}],
vinCode: [ {required: true, message: '不能为空', trigger: 'blur'}],
contacts: [ {required: true, message: '不能为空', trigger: 'blur'}],
remark: [
{required: false, message: '', trigger: 'blur'},
{type: 'string', max: 30, message: '最多可录入30个字符', trigger: 'blur'}
]
}
}
},
methods:{
//增加或修改车辆
doSave () {
let self = this;
self.$refs.entity_edit.validate((valid) => {
if (valid) {
let req = self.saveUrl;
if (self.$util.isNotEmpty(self.entity.id)) {
req = self.updateUrl;
}
self.$axios.post(req, self.entity).then(res => {
if(res.code==200){
self.$Message.success('操作成功!');
self.$emit('refresh-data'); // 触发刷新数据的事件
self.$emit('close-form'); // 触发关闭表单的事件
}else{
self.$Message.error(res.msg);
}
}).catch(e =>{
self.show_edit_win = true;
});
}
});
},
}
}
</script>
<style scoped>
</style>
3. 代码关键点解释
1 父组件向子组件传递数据:
-
父组件通过
props
向子组件传递数据。在这个例子中,父组件将parentFormData
传递给了子组件FormComponent
,子组件通过props
接收这个数据。 -
子组件不能直接修改传入的
props
,因为props
是不可变的。如果子组件需要修改这些数据,通常会将props
的数据复制到本地状态(如localFormData
),然后操作本地数据。
2 子组件调用父组件方法
子组件不能直接调用父组件的方法。但我们可以通过事件通信的方式,让子组件触发事件,父组件监听事件并调用相应的方法。这种通信方式是 Vue 框架推荐的做法,符合单向数据流的设计思想
具体步骤:
- 父组件定义方法:父组件中定义关闭表单和刷新数据的方法。
- 子组件触发事件:子组件通过
$emit
触发自定义事件。 - 父组件监听事件:父组件通过事件监听器捕获子组件触发的事件,并执行对应的方法。
-
子组件通过
$emit
触发自定义事件。在表单提交后,子组件使用this.$emit('close-form')
触发事件,通知父组件关闭窗口;同样使用this.$emit('refresh-data')
触发事件,通知父组件刷新数据。 -
父组件通过事件监听器监听事件。父组件通过
@close-form="handleCloseForm"
和@refresh-data="refreshData"
监听子组件触发的事件,并在事件发生时执行相应的方法。
4. 实现后的效果
以上代码实现的效果图如下:
父组件的【新增】使用子组件后如下:
父组件的【详情】使用子组件后如下:
三、总结
在 Vue 的组件化开发中,父组件与子组件的通信是常见需求。通过 props
,父组件可以向子组件传递数据;通过 $emit
事件,子组件可以通知父组件执行相应的操作。这样的单向数据流和事件通信机制,保证了组件之间的解耦和代码的可维护性。