封装element-plus组件,简化表单(一)

535 阅读3分钟

上一篇大致介绍了封装组件之后的效果,今天有空来说下我是如何简化表单的

我的封装思路主要是如何简化代码,去除冗余字段,简化书写逻辑

个人观点: 在我们日常工作中表单使用的频率绝对是最高的一个,如何快速完成这个步骤,市面上的低代码平台拖拽式操作就能轻松完成一个表单,但这种对于我们这些程序员其实不大适用,低代码生成出来的代码还是一坨又一坨的屎山,简直不忍直视啊,所以写了这么久的代码,我更加希望我得代码能够用更少的代码实现更多的功能,并且提升代码可读性,可维护性,这个过程是漫长的,只能一步一个脚印的慢慢摸索。

首先看下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-modelel-form-item中的prop是不是重复的? 那么该怎么优化?我再次对el-from进行封装,并使用provideinject跨组件通信,提供了getModelValuesetModelValue方法

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构建的,所以积累了一些经验希望能帮助到大家。