组件二次封装需要掌握的前置知识:
1.父子组件的传参方式
1.1: v-model:在父组件的子组件标签上用v-model绑定需要传递的值,在子组件中的defineprops中定义接收.
父组件:
子组件:
需要注意的是,父组件绑定时如果使用v-model:属性名来规定传入的属性名,子组件接收时需要保持一致,否则使用默认的modelValue
1.2: ref绑定:在父组件中的子组件标签上绑定ref值,且将其声明为ref响应式变量,此后就可以使用 变量名.value.子组件属性名 来获取子组件中的属性和方法.需要注意的是:子组件中的属性和方法必须通过defineExpose暴露父组件才能获取
父组件:
子组件:
2.v-for v-if条件渲染
3.插槽的基本使用
----------------------------------------------------------分割线--------------------------------------------------------------
准备好了前置知识就开始动手封装
首先从element-plus官网中复制一个form表单到子组件中
这个里面包含的东西挺全,就选这个,把代码复制到子组件中就生成了这个表单
在动手之前先把思路理一下:我们封装组件的目的主要是方便复用,能够做到灵活的定制组件中的内容,并且能够无障碍的调用组件中的相关属性和方法. 如此一来就可以把封装分成两个层面:1.业务层处理组件内容的条件渲染, 而方便复用定制的方式莫过于通过数据驱动 2.数据层处理父子组件之间的数据方法传递
首先看业务层:
我们观察el-form的html结构
它通过el-form标签确定表单组件, 通过el-form-item标签包裹的具体组件来把元素放进表单, 标签中的v-model是绑定的响应式数据. 到这里,我们可以确定两个配置数据,一个用来规定表单里有什么的结构数据,一个用来确定表单内容绑定什么的响应式数据, 我们先搞结构数据,如下
export default {
labelWidth: "120px",
formItems: [
{
field:'username',
label: '用户名',
type: 'input',
placeholder: '请输入用户名'
},
{
field:'address',
label: '地址',
type: 'input',
placeholder: '请输入现住址'
},
{
field:'password',
label: '密码',
type: 'password',
placeholder: '请输入密码'
},
{
field:'selectValue',
label: '选择城市',
type: 'select',
placeholder: '请选择城市',
options: [
{
label: '北京',
value: 1
},
{
label: '上海',
value: 2
}
]
},
{
field:'status',
label: '状态',
type: 'switch',
},
{
field:'date',
label: '时间',
type: 'date',
},
{
field:'checkValue',
label: '多选框',
type: 'checkBoxGroud',
checkboxes: [
{
name: 'name',
label: '多选1'
},
{
name: 'name',
label: '多选2'
},
{
name: 'name',
label: '多选3'
}
]
},
{
field:'radioValue',
label: '单选框',
type: 'radioBoxGroud',
radios: [
{
label: '单选1'
},
{
label: '单选2'
},
]
},
{
field:'textAreaValue',
label:'输入框',
type:'textarea',
}
]
}
其中的type决定了往表单中放入什么标签,label则是这个标签的标题, 有了这个结构数据我们就可以通过v-for v-if来进行条件渲染了,篇幅原因贴出代码的一部分
其中的formItems就是上文中的结构数据,具体的表单内容则用v-if判断type来条件渲染,到此业务层通过数据驱动表单结构完成.
然后开始盘数据层:
前文中提到组件上的v-model绑定的是表单中写入的数据,他是一个固定的变量,但是既然要复用,就不能把这个变量写死了.那如何让他动态的绑定我想要的变量呢. 我们回看上文的结构数据,里面有一个没用到的field属性,我们是否可以对这个结构数据进行循环遍历然后把这个字段组合成一个新的数据数组呢,这样就可以完成:根据自己规定的结构数据动态的生成响应式数组→动态的绑定v-model, 思路确定了就开干.
首先定义一个空对象formData,然后遍历结构数据把所有的field属性加入formData中,再把formData转换成一个响应式数据.然后就可以进行愉快的绑定了
其中modelValue就是我们处理好的响应式数据refFormData,不同的组件绑定方式可能有所不同,这一点需要在element-plus示例中进行确认
到现在为止表单的基础展示和编辑已经实现了,下面就需要把文件该拆分的拆分:
这里我分成了两层:第一层为子组件负责表单的条件渲染以及事件功能
第二层为父组件(封装完在页面中直接调用的一层)负责向一层传递结构数据以及向外发送事件和数据,起到一个链接作用 以下是完整代码:
第一层:FormSet.vue
<template>
<slot name="header"></slot>
<el-form ref="formDataRef" :model="modelValue" :label-width="labelWidth">
<template v-for="(item) in formItems" :key="item.label">
<el-form-item :label="item.label" :prop="item.field">
<template v-if="item.type === 'input' || item.type === 'password'">
<el-input v-model="modelValue[item.field]" :placeholder="item.placeholder" />
</template>
<template v-else-if="item.type === 'select'">
<el-select v-model="modelValue[item.field]" :placeholder="item.placeholder">
<template v-for="item1 in item.options" :key="item1.value">
<el-option :label="item1.label" :value="item1.value" />
</template>
</el-select>
</template>
<template v-else-if="item.type === 'switch'">
<el-switch v-model="modelValue[item.field]" />
</template>
<template v-else-if="item.type === 'date'">
<el-col :span="11">
<el-date-picker v-model="modelValue[item.field]" type="date" placeholder="Pick a date" style="width: 100%" />
</el-col>
</template>
<template v-else-if="item.type === 'checkBoxGroud'">
<el-checkbox-group v-model="modelValue[item.field]">
<template v-for="(item1, index) in item.checkboxes" :key="index">
<el-checkbox :label="item1.label" :name="item1.name" />
</template>
</el-checkbox-group>
</template>
<template v-else-if="item.type === 'radioBoxGroud'">
<el-radio-group v-model="modelValue[item.field]">
<template v-for="(item1, index) in item.radios" :key="index">
<el-radio :label="item1.label" />
</template>
</el-radio-group>
</template>
<template v-else-if="item.type === 'textarea'">
<el-input v-model="modelValue[item.field]" type="textarea" />
</template>
</el-form-item>
</template>
</el-form>
<slot name="footer"></slot>
</template>
<script lang="ts" setup>
import { reactive,ref } from 'vue'
import {FormInstance} from 'element-plus'
const formDataRef = ref<FormInstance>()
const props = defineProps({
formItems:{
type:Array
},
labelWidth:{
type:String,
default:'120px'
},
modelValue:{
type:Object
}
})
const btnSub = ()=>{
//console.log(props.modelValue);
}
const resetForm=()=>{
console.log('222');
formDataRef.value?.resetFields()
}
defineExpose({
btnSub,
resetForm
})
</script>
第二层:MyForm.vue
<template>
<FormSet v-bind="formConfig" v-model="refFormData" ref="formset">
<template #header>
<h1>标题</h1>
</template>
<template #footer>
<button @click="btnSubmit">确定</button>
<button @click="btnReset">取消</button>
</template>
</FormSet>
</template>
<script setup lang="ts">
import FormSet from './FormSet.vue';
import { reactive, ref } from 'vue';
import formConfig from "@/components/formSetConfig"
let formset = ref()
let formData ={}
formConfig.formItems.forEach(item=>{
console.log(item.type,item.field);
if(item.type ==='checkBoxGroud'){
formData[item.field] = []
}else{
formData[item.field]=''
}
})
let refFormData = ref(formData)
const btnSubmit= ()=>{
formset.value.btnSub()
emit('formDataChange',refFormData.value)
}
let emit = defineEmits(['formDataChange'])
const btnReset=()=>{
formset.value?.resetForm()
}
</script>
结构数据配置文件:
export default {
labelWidth: "120px",
formItems: [
{
field:'username',
label: '用户名',
type: 'input',
placeholder: '请输入用户名'
},
{
field:'address',
label: '地址',
type: 'input',
placeholder: '请输入现住址'
},
{
field:'password',
label: '密码',
type: 'password',
placeholder: '请输入密码'
},
{
field:'selectValue',
label: '选择城市',
type: 'select',
placeholder: '请选择城市',
options: [
{
label: '北京',
value: 1
},
{
label: '上海',
value: 2
}
]
},
{
field:'status',
label: '状态',
type: 'switch',
},
{
field:'date',
label: '时间',
type: 'date',
},
{
field:'checkValue',
label: '多选框',
type: 'checkBoxGroud',
checkboxes: [
{
name: 'name',
label: '多选1'
},
{
name: 'name',
label: '多选2'
},
{
name: 'name',
label: '多选3'
}
]
},
{
field:'radioValue',
label: '单选框',
type: 'radioBoxGroud',
radios: [
{
label: '单选1'
},
{
label: '单选2'
},
]
},
{
field:'textAreaValue',
label:'输入框',
type:'textarea',
}
]
}
注意和踩坑
1.
form组件中的prop必须绑定且必须和v-model绑定的字段相同,否则会导致el-form自带的重置表单方法resetFields()失效
2.
使用绑定事件+emit实现子向父数据传递时,红框中的方法不需要加括号和形参,加上反而会导致拿不到子传来的数据