这篇“vue如何封装一个高质量的表单通用组件”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“vue如何封装一个高质量的表单通用组件”文章吧。
基于Element-plus实现二次封装表单组件
特性复用:必须继承原有组件的所有特性。
命名规范:二次组件名必须见名知意,我们一般都是起一个公用名+原有组件名,比如lib-form。
接口简单:自定义暴露出来的接口越简单越好。
容易拓展:留有自定义插槽,让用户可以自己选择。
功能完善:具备更完善的功能如:表单验证、动态删减表单,集成第三方的插件(富文本)...
场景通用:具备多个场景使用,比如弹框嵌套表单、页面嵌套表单。
封装一个高质量的通用组件,上面是真的只是基操,话不多说,直接上实践手把手教你封装组件。
步骤1
继承原有组件的所有特性(这也是封装的核心)。先明确三个大方向:
表单固定属性,继承Form表单的所有属性、方法。
// 定义el-form的ref继承原有组件的form属性 export interface FormInstance { registerLabelWidth(width: number, oldWidth: number): void, deregisterLabelWidth(width: number): void, autoLabelWidth: string | undefined, emit: (evt: string, ...args: any[]) => void, labelSuffix: string, inline?: boolean, model?: Record<string, unknown>, size?: string, showMessage?: boolean, labelPosition?: string, labelWidth?: string, rules?: Record<string, unknown>, statusIcon?: boolean, hideRequiredAsterisk?: boolean, disabled?: boolean, validate: (callback?: Callback) => Promise<boolean>, resetFields: () => void, clearValidate: (props?: string | string[]) => void, validateField: (props: string | string[], cb: ValidateFieldCallback) => void, }
表单项固定属性,继承表单项的所有属性、方法。
// 表单每一项的配置选项 export interface FormOptions { // 表单项显示的元素 type: '',// 定义表单项类型 value?: any, // 表单项的值 label?: string,// 表单项label prop?: string,// 表单项的标识 rules?: RuleItem[],// 表单项的验证规则 placeholder?: string,// 表单项的占位符 attrs?: { // 按需定义不同表单类型属性 ... }, children?: FormOptions[],// 表单项的子元素,可能存在嵌套表单组件,如select ....// 适当扩展我们需要的属性,比如上传组件属性,行布局表单属性 }
由于Element-plus组件都是以el-为前缀,所以type取值只需要取el-后面部分作为值就行,比如el-input取 'input' 为值。
表单验证效果,继承组件原有的所有验证属性。由于Element-plus的验证都是使用 async-validator 这个插件的验证方法,直接复用插件源码路径async-validator/src/interface.ts文件下的所有代码:
// 核心代码:封装验证方式时的属性 export interface RuleItem { type?: RuleType; // 验证种类 required?: boolean;// 是否必填 pattern?: RegExp | string;// 验证方式匹配 min?: number; // 表单项最小值 max?: number; // 表单项最大值 len?: number; // 表单项字符长度 trigger?: string | string[];// 验证触发方式 .... }
步骤2
实现一个完善的通用组件封装,通过对标签封装、接口 暴露、开发者传参等。明确表单类型,根据不同类型表单复用多种场景,不仅开发者用户拓展,而且,让开发者用最少代码就可以复用:
<!--表单框架:model就是传入的表单对象,rules就是传入的验证对象 --> <el-form v-if="model" :validate-on-rule-change="false" v-bind="$attrs" :model="model" :rules="rules" ref="form" > ... <!--表单项封装 --> </el-form>
普通表单项封装,比如日期、输入等组件。
<template v-for="(item, index) in options" :key="index"> <el-form-item :label="item.label" :prop="item.prop"> <component v-else :is="`el-${item.type}`" v-bind="item.attrs" v-model="model[item?.prop!]" > </component> </el-form-item> </template>
嵌套表单项封装,比如下拉框,除了select组件还嵌套option组件。
<template v-for="(item, index) in options" :key="index"> <el-form-item v-if="item.children && item.children.length" :label="item.label" :prop="item.prop" > <component :is="`el-${item.type}`" v-bind="item.attrs" v-model="model[item?.prop!]" > <component v-for="(child, i) in item.children" :key="i" :label="child.label" :value="child.value" :is="`el-${child.type}`" > </component> </component> </el-form-item> </template>
富文本表单项封装
本文使用的是wangEditor。
<div id="editor" v-else-if="item.type === 'editor'"></div>
import E from 'wangeditor'; // 遍历传入的prop的options对象,初始化富文本 if (item.type === 'editor') { // 初始化富文本 nextTick(() => { if (document.getElementById('editor')) { const editor = new E('#editor'); editor.config.placeholder = item.placeholder!; editor.create(); // 初始化富文本的内容 editor.txt.html(item.value); editor.config.onchange = (newHtml: string) => { model.value[item.prop!] = newHtml; }; edit.value = editor; } }); }
上传表单项封装
向开发者暴露上传的核心方法:预览、删除、上传成功等,同时允许开发者自定义上传信息以及渲染区域等。
<el-form-item :label="item.label" :prop="item.prop"> <!-- 上传表单 --> <el-upload v-if="item.type === 'upload'" v-bind="item.uploadAttrs" :on-preview="onPreview" :on-remove="onRemove" :on-success="onSuccess" :on-error="onError" :on-progress="onProgress" :on-change="onChange" :before-upload="beforeUpload" :before-remove="beforeRemove" :http-request="httpRequest" > <slot name="uploadArea"></slot> <slot name="uploadTip"></slot> </el-upload> </el-form-item>
同行多个表单布局封装
有时业务需要,一行可以定义多个表单,所以需要使用el-row,此时需要修改FormOptions属性接口,完善多个表单场景,cols是一个数组定义FormOptions数组,colOption是el-col组件的相关属性,然后重新复用嵌套表单的代码。
<template v-if="item.type === 'row'"> <el-row :gutter="item.rowGutter"> <el-col v-for="(jtem, jndex) in item.cols" v-bind="jtem.colOption" :key="jndex" > <el-form-item :label="jtem.label" :prop="jtem.prop"> <component :is="`el-${jtem.type}`" v-bind="jtem.attrs" v-model="model[jtem?.prop!]" > <template v-if="jtem.children && jtem.children.length"> <component v-for="(child, i) in jtem.children" :key="i" :label="child.label" :value="child.value" :is="`el-${child.type}`" > </component> </template> </component> </el-form-item> </el-col> </el-row> </template>
自定义插槽:开发者可以根据需要,在封装的el-form中添加插槽,可以允许组件功能的拓展,我们可以根据自己需要进行封装,这里就不一一演示了。
提交取消按钮区域:这个最好可以实现插槽让开发者可以自定义。
<el-form-item> <slot name="action" :form="form" :model="model"></slot> </el-form-item>
步骤3
开发者的调用封装组件,通过配置不同表单类型的数组,然后调用lib-form封装组件实现业务代码复用。
组件的调用:根据业务需要,可以适当定义我们需要的组件属性以及必须要传的参数。
<lib-form ref="form" label-width="100px" :options="options" @on-change="handleChange" @before-upload="handleBeforeUpload" @on-preview="handlePreview" @on-remove="handleRemove" @before-remove="beforeRemove" @on-success="handleSuccess" @on-exceed="handleExceed" > <template #uploadArea> <el-button size="small" type="primary">Click to upload</el-button> </template> <template #uploadTip> <div > jpg/png files with a size less than 500kb </div> </template> <template #action="scope"> <el-button type="primary" @click="submitForm(scope)">提交</el-button> <el-button @click="resetForm">重置</el-button> </template> </lib-form>
表单项的配置数组:由于这配置数组比较长,所以一般可以单独抽离出来,不要写在vue文件中,这样可以提高代码的可读性。
// 这里以多行表单布局为例子 let options: FormOptions[] = [ { type: 'row', rowGutter: 20, cols: [ { type: 'input', value: '', label: '用户名', prop: 'username', placeholder: '请输入用户名', rules: [ { required: true, message: '用户名不能为空', trigger: 'blur', }, { min: 2, max: 6, message: '用户名在2-6位之间', trigger: 'blur', }, ], attrs: { clearable: true, }, colOption: { offset: 0, span: 12, }, }, { type: 'input', value: '', label: '用户名', prop: 'username', placeholder: '请输入用户名', rules: [ { required: true, message: '用户名不能为空', trigger: 'blur', }, { min: 2, max: 6, message: '用户名在2-6位之间', trigger: 'blur', }, ], attrs: { clearable: true, }, colOption: { offset: 0, span: 12, }, }, ], }, ]