上一篇大致介绍了封装组件之后的效果,今天有空来说下我是如何简化表单的
我的封装思路主要是如何简化代码,去除冗余字段,简化书写逻辑
个人观点: 在我们日常工作中表单使用的频率绝对是最高的一个,如何快速完成这个步骤,市面上的低代码平台拖拽式操作就能轻松完成一个表单,但这种对于我们这些程序员其实不大适用,低代码生成出来的代码还是一坨又一坨的屎山,简直不忍直视啊,所以写了这么久的代码,我更加希望我得代码能够用更少的代码实现更多的功能,并且提升代码可读性,可维护性,这个过程是漫长的,只能一步一个脚印的慢慢摸索。
首先看下elementplus官网上form表单组件的第一个例子
<el-form :model="form" label-width="auto" style="max-width: 600px">
<el-form-item label="Activity name">
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="Activity zone">
<el-select v-model="form.region" placeholder="please select your zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
<el-form-item label="Activity time">
<el-col :span="11">
<el-date-picker v-model="form.date1" type="date" placeholder="Pick a date" style="width: 100%" />
</el-col>
<el-col :span="2" class="text-center">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<el-time-picker v-model="form.date2" placeholder="Pick a time" style="width: 100%" />
</el-col>
</el-form-item>
<el-form-item label="Instant delivery">
<el-switch v-model="form.delivery" />
</el-form-item>
<el-form-item label="Activity type">
<el-checkbox-group v-model="form.type">
<el-checkbox value="Online activities" name="type">
Online activities
</el-checkbox>
<el-checkbox value="Promotion activities" name="type">
Promotion activities
</el-checkbox>
<el-checkbox value="Offline activities" name="type">
Offline activities
</el-checkbox>
<el-checkbox value="Simple brand exposure" name="type">
Simple brand exposure
</el-checkbox>
</el-checkbox-group>
</el-form-item>
<el-form-item label="Resources">
<el-radio-group v-model="form.resource">
<el-radio value="Sponsor">Sponsor</el-radio>
<el-radio value="Venue">Venue</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="Activity form">
<el-input v-model="form.desc" type="textarea" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="onSubmit">Create</el-button>
<el-button>Cancel</el-button>
</el-form-item>
</el-form>
可以看到表单是由el-form + el-form-item +基础组件构成这一堆代码
这一堆代码中其中有部分是重复的,重复的部分就存在于 el-form-item和基础组件之中
那么我该如何简化这部分代码?以el-input为例我创建了一个formNode的组件,二次封装el-input命名成els-input
els-form-node:
<script setup lang="ts">
import { ref, computed } from 'vue'
import { formItemProps } from '@/props'
import { useProps } from '@/hooks'
import ElsFormItem from '@/elementui/form-item'
defineOptions({ name: "ElsFormNode" })
const props = defineProps(formItemProps)
const { getValue,attrProps } = useProps(props)
const container = getValue<string>('tagContainer', '')
const formItem = ref()
const hasFormItem = computed(() => {
if (props.createFormItem !== undefined) {
return props.createFormItem
}
if (container == 'form') {
return true
}
return false
})
</script>
<template>
<ElsFormItem v-if="hasFormItem" v-bind="attrProps" ref="formItem" class="els-node">
<slot></slot>
</ElsFormItem>
<slot v-else></slot>
</template>
els-input:
<script setup lang="ts">
import { ref, watch } from 'vue'
import { useProps, useModel } from '@/hooks';
import { inputEmits } from 'element-plus'
import { inputProps } from './input';
import ElsFormNode from '@/custom/form-node'
defineOptions({
name: 'ElsInput',
inheritAttrs: false
})
defineEmits(inputEmits)
const props = defineProps(inputProps)
const {
currModelValue,
returnModelValue,
} = useModel(props)
const { formNodeProps, componentProps } = useProps(props)
const inputValue = ref()
watch(currModelValue, (val) => {
const currValue = val
if (currValue) {
inputValue.value = currValue
}
}, { immediate: true })
watch(inputValue, (val) => {
handleReturnResult(val)
})
function handleReturnResult(val) {
returnModelValue(val)
}
</script>
<template>
<ElsFormNode v-bind="formNodeProps" childType="input">
<el-input v-bind="componentProps" v-model="inputValue">
</el-input>
</ElsFormNode>
</template>
这样我在使用els-input时候就可以直接使用el-from-item的属性,省去了el-form-item,不过后面随着封装的越多,发现这个写法还是太复杂,每次都要去包一层els-from-node,麻烦!太麻烦了!怎么办?类、继承,重载,没错,我去看了下jsx写法,于是改成了这样:
export class TBaseFormItem<T> extends TBaseCom<T> implements IBaseCom {
public _formNode=ref(null)
public _formNodeProps=ref()
public _componentProps=shallowRef()
constructor(props: T,ctx) {
super(props,ctx)
}
pcRender(): JSX.Element | null {
return null // 默认实现
}
mobileRender(): JSX.Element | null {
return null // 默认实现
}
render() {
if (!this.mobileRender && !this.pcRender) {
return <>
<el-tag type="warning">渲染方法未实现</el-tag>
</>
}
return <>
<ElsFormNode ref={this._formNode} {...this._formNodeProps.value} >
{this._isMobile.value&&this.mobileRender ? this.mobileRender() : this.pcRender()}
</ElsFormNode>
</>
}
}
els-input也改成了这样:
<script lang="ts">
import { defineComponent } from 'vue';
import { inputProps, Input } from './input';
import { inputEmits } from 'element-plus'
export default defineComponent({
name: 'ElsInput',
inheritAttrs: false,
props: inputProps,
setup(props, ctx) {
const input = new Input(props, ctx)
return () => input.render()
},
emits: inputEmits
})
</script>
export class Input extends TBaseFormItem<InputProps> {
private inputValue = ref()
private encodeValue = ref()
constructor(props: InputProps, ctx) {
super(props, ctx)
watch(this._currModelValue, (val) => {
const currValue = val
if (currValue) {
if (this._props.encode) {
this.encodeValue.value = currValue;
if (this._props.encodeType === "url") {
this.inputValue.value = decodeURIComponent(this.encodeValue.value);
}
} else {
this.inputValue.value = currValue
}
}
}, { immediate: true })
watch(this.inputValue, (val) => {
returnResult(val)
})
const returnResult = (val) => {
let currValue = val
this._returnModelValue(currValue)
}
}
pcRender() {
return <>
<el-input
{...this._componentProps.value}
v-model={this.inputValue.value}>
{{
...this._slots
}}
</el-input>
</>
}
}
二次封装的组件只要继承TBaseFormItem就可以了,果然多想想,咱们得代码还是有提升空间的吧,嘻嘻~
好了,现在已经将el-form-item和组件融合在了一起,再看看代码,是不是还有优化的空间?对,示例中v-model和el-form-item中的prop是不是重复的?
那么该怎么优化?我再次对el-from进行封装,并使用provide和inject跨组件通信,提供了getModelValue和setModelValue方法
function getModelValue(key) {
if (key === undefined || key === '') {
return
}
return modelValue.value[key]
}
function setModelValue(key, value, aIndex = -1) {
modelValue.value[key] = value
}
所以els-input中的值只要有变动 我就会根据prop将表单中的对象值改变,从而实现了无需设置v-model简化了这段代码。
最后看下将elementplus官网的代码简化后:
<els-form v-model="form" label-width="auto" style="max-width: 600px">
<els-input label="Activity name" prop="name" />
<els-select label="Activity zone" prop="region" placeholder="please select your zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</els-select>
<els-form-item label="Activity time">
<el-col :span="11">
<els-date-picker prop="date1" type="date" placeholder="Pick a date"
style="width: 100%" />
</el-col>
<el-col :span="2" class="text-center">
<span class="text-gray-500">-</span>
</el-col>
<el-col :span="11">
<els-time-picker prop="date2" placeholder="Pick a time" style="width: 100%" />
</el-col>
</els-form-item>
<els-switch label="Instant delivery" prop="delivery" />
<els-checkbox label="Activity type">
<els-option>Online activities</els-option>
<els-option>Promotion activities</els-option>
<els-option>Offline activities</els-option>
<els-option>Simple brand exposure</els-option>
</els-checkbox>
<els-radio prop="resource" label="Resources">
<els-option>Sponsor</els-option>
<els-option>Venue</els-option>
</els-radio>
<els-input label="Activity form" prop="desc" type="textarea" />
<els-form-item>
<el-button type="primary" @click="onSubmit">Create</el-button>
<el-button>Cancel</el-button>
</els-form-item>
</els-form>
好了,今天就先写这么多吧。
使用elementui已经有五六年之久,公司的后台也是用vue+elementui构建的,所以积累了一些经验希望能帮助到大家。