一、场景介绍1、业务场景中如何定义一个“巨大”的表单,因人而异。但是如果只有收货人信息、登录、注册等一些简单的表单,那肯定不会很大,直接写模板进行常规开发就可以了。没有必要为了配置而配置~从笔者的理解来看,表单项有很多。比如笔者负责的“投递系统”,随便提交就会涉及几十个甚至上百个字段。这样,整个表单将由几十个或上百个表单项组成。上面是一个巨大的表格。先放一张成品小截图给大家看看~别看截图好像表单项就那样。根据右栏的统计,一共是40+,但这只是初版,还有很多字段没有打通。许多表单项是链接的,可以添加或删除,许多表单项是隐藏的。相信你很难想象。其实只需要进行简单的配置就可以实现上图中的界面。比如下图中的js对象就是上图中几个表单项的配置:不难看出,配置的思路其实就是对表单项进行抽象,制定一个协议来描述每个表单项。具体对象中各个属性的用途是什么?这个作者在后面说我的设计思路的时候会详细介绍~这时候你肯定有疑问,为什么抽象,为什么配置方案比较好,我们往下看~2.配置的思想是复用性高,易于维护。是的,作者采用配置方式开发表单,完全是为了高复用性和可维护性,进而提高开发效率,解放生产力。templatefacebooktiktok的巨大引擎具有广泛的可维护性:它不只是制造了一些东西,它就结束了。首先,渠道方会推出新的配置功能,这是不可控的。其次,系统开发的时候,并不是要访问所有字段,而是先访问业务方需要的核心配置,所以后面访问新字段的需求会很多。接下来我会举两个例子来说明高复用和好维护在表1中体现在哪些地方。代码如下:...复制代码Form2.虽然与Form1相似,但还是有区别的。比如表单2的活动区域就不叫“活动区域”,表单项之间的联动关系也不一样。然后我们用**复制大法**来做。代码如下:...复制代码的方式虽然好用,但是我们的复用性基本不行现在所有的功能几乎都是重新开发的,这使得它非常被动。虽然上面的例子看起来很容易实现,但是作者已经说过,我们要开发的是一个有数百个表单项的系统。当模板量累积到一定程度,你就会想吐血。好不容易写了几千行模板,以为完了,然后接一个新媒体,从新开始。。。而且,还得再写几千行模板和各种表格个人之间的联动逻辑items也是很痛苦的。。。所以,如何提高复用性,如何让复杂的表单清晰易维护,是笔者的出发点~2.Design&Implementation1.Designprotocol首先我们思考一下每一个是什么我们的表单项需要:inputselectradiolabel表单项的名称/描述。formKey字段名称。我们向后段的name字段提交数据,比如form.name的'name'值,用来存放表单值。multipledisabled值是否绑定到显示的表单上的v-model?有了以上几点,我们尝试用一个协议来表达case中的form1:...复制代码我们可以使用协议来描述它[{type:'el-input',label:'activityname',formKey:'name',value:'',//默认值为空字符串options:{vIf:[//表示:当form.area==='area1'时,只显示{relationKey:'area',value:'area1'}]}},{type:'el-select',label:'activearea',formKey:'area',value:'area1',options:{multiple:true}}]不是复制代码有点好笑?从开发一个巨大的表单系统转向编写JSON不是很酷吗?2.渲染器配置是可以实现的,但是如何将配置转化为我们真正的形式呢?如果直接开始,我想大部分都会先这样开始,比如:...复制代码,我们来看看模板上面中,你有没有发现很多冗余代码?如果我们需要将props传递给示例中的disabled和multiple等组件;控制v-if等等。.我们有多少组件,这些重复的代码就得写多少次。以后如果要给所有组件多传递一个props,就要编辑n次~切记!我们的配置是为了提高效率,所以这是不行的~这里笔者推荐写render函数。render函数的场景&相应的好处,可以看看官方文档[1]的解释~render函数vuerender函数.vue文件templaterender函数render函数vNodevue2render(h)=>h(App)里面说的是React写jsx比Vue写模板写逻辑好,那我们也用render函数写逻辑~:stuck_out_tongue_closed_eyes:(当然如果你不是特别熟悉render函数,也可以写模板)接下来我们看如何通过render函数制作我们的表单项,以上面的一种情况为例:这段复制代码如何通过render函数来表达?根据官方文档,我们只需要搞清楚这三个参数是什么:createElement('div',//{String|Object|Function},一个HTML标签名,组件选项对象,或者...{},//模板中属性对应的数据对象[]//{String|Array},可以理解为子节点)复制代码接下来我们直接开始:App组件中引用了FormItemDemo组件,代码如下
copycode此时,我们的输入表单项就出现在页面上了。初步工作已经完成。接下来就是将render函数的一些动态数据替换成变量,结合我们的配置config3.renderfunction&configurationdata要说render函数并不是真的很完美,毕竟还得实现指令如v-if和v-model自己写,但是没问题,给我带来了很大的方便,所以我可以接受。在正式演示配置实现时,笔者首先声明一点:这里只是demo级别,具体到项目实战还是要看业务场景。我做业务的时候封装了一层select,cascader等组件。因为很多时候我们的下拉数据需要从后端取,经过封装后,组件可以通过传入的params和urlPath来获取数据。所以大家要多关注idea,然后根据业务场景自己去思考和实现。首先配置数据如下:exportdefault[{type:'el-input',label:'activityname',formKey:'name',value:'',//默认值为空字符串options:{vIf:[//表示:仅当form.area==='area1'时显示{relationKey:'area',value:'area1'}]}},{type:'el-select',label:'activearea',formKey:'area',value:'area1',options:{multiple:true},optionData:[//这里模拟到后端拉回数据{label:'area1',value:'area1'},{label:'Area2',value:'area2'}]}]复制代码我们把渲染函数改成这样复制代码接下来,我们在app组件中同时应用我们的配置+FormItemDemo组件:
复制代码现在让我们看看页面是什么样的?好的!!!实现了,接下来我们只需要根据业务需求丰富我们的FormItemDemo组件即可在这里,笔者就带领大家实现一个联动显示和隐藏,下拉框多选功能~相信看完之后,你一定会有豁然开朗的感觉,然后就可以根据业务需求实现了你自己。4.丰富组件能力实现业务(1)当活动区域的值为“area1”时,会显示活动名称。我们先来看第一个需求:分析这个需求,我们的input组件是和select组件挂钩的,所以input组件需要获取select组件的值。这时候我们可以将整个config传递给app组件中的FormItemDemo组件。回头看我们的配置,我们把显示和隐藏的配置放在options.vIf中(笔者这里设计成一个数组,因为在业务中经常遇到一个表单会被几个表单值链接起来),所以FormItemDemo组件需要用这个来判断是否执行这个render来实现v-if。如图:笔者使用了一个computed来实现这个需求。不用深究,只知道componentShow的作用就是在链接的relationKey的config中查找value,判断是否与配置一致。computed:{componentShow(){constvIfArr=this.itemConfig?.options.vIfif(!vIfArr)returntrueconstrelationArr=this.config.filter(config=>vIfArr.find(vIf=>vIf.relationKey===config.formKey))for(constrelationItemofrelationArr){constvIfItem=vIfArr.find(_=>_.relationKey===relationItem.formKey)//这里是判断链接的表单值是否不满足条件可以显示,不满足则不显示render开始时的计算属性就ok了,看看就下载结果吧!两个表单项的联动完成了select多选、单选和添加过滤属性的控制……(2)接下来的需求,大家可以想想怎么实现。其实,都是一样的。这样,你就基本完成了。只要实现了FormItemDemo的业务逻辑,以后只需要配置N个表单系统即可。钓鱼在船上。真是让人头疼的事情~可是,优秀的前端怎么可能就这么忘了呢?无论如何,我们必须做一些优化,对吧?3.静态配置。细心的朋友可能已经发现,我们在上面实现configuration的时候,直接将整个config赋值给data,然后在App组件的el-form中使用v-for。那么它必然会出现。一些尴尬的事情,比如,我们看下图:是的,正如你所看到的,所有的属性都有getter和setter,这意味着它们都被初始化为响应式的。由于我们的业务非常复杂,当我们真的要用一个config来描述整个表单的时候,config的规模远不止上面这些,整个配置对象的层级可能更深。如果是这样,则可能会出现性能问题。熟悉Vue2的同学都知道,在初始化时,会对数据进行深度遍历,为响应数据添加get和set,组件执行render函数时,会访问这些对象的属性。一旦访问,就会触发数据属性的依赖收集动作。如果属性太多,会不假思索地执行get方法。这绝对不符合我们优秀的前端风格吧?怎么办,优化一下。我们自己不去想了,直接玩玩游达的搬运吧,哈哈哈。:stuck_out_tongue_closed_eyes:深入阅读过Vue2源码的同学一定对属性__ob__不陌生。上面的截图也有这个属性,但是大家发现ob属性没有对应的get和set。我们打开源码看看裕达都做了什么?首先,在响应式处理之前,调用了一个def方法。这里第四个参数不传。看def的具体实现。其实就是重新定义这个对象的属性。由于enumerable没有通过,此时__ob__的enumerable为false这有什么用呢?总之,这个属性是不能遍历的,后续响应式初始化的时候也会跳过这个属性。不清楚的朋友可以看看作者写的一个demo加深理解:是的,我们这里也是用同样的方法优化我们的config无响应。其实对于整个config数据,我们只需要保证value是responsive即可,其他很多描述性数据是不需要的。然后我们优化其他字段~//优化函数functionoptimize(array){returnarray.reduce((acc,cur)=>{for(constkeyofObject.keys(cur)){if(key==='value')continue//对非值属性的无响应优化Object.defineProperty(cur,[key],{enumerable:false})}acc.push(cur)returnacc},[])}复制代码将功能的实现就不详细介绍了,大家心里有数就好了(有兴趣的可以仔细看看)~说到这里,我们再打印一下config,看看长啥样:好了,这就舒服了~终于搞定了!!!写在最后,其实这是我差不多一年前的实践,一直在分享和不分享之间徘徊。因为这类文章不会有太大的共鸣,也不会给我带来流量和影响力,但是好吧,也许之前有朋友遇到过和我一样的情况,或者你有更好的见解建议可以提高我,所以我决定无论如何分享它们。通常,更多的开发伙伴会抱怨自己每天都在业务上苦苦挣扎,项目没有亮点。这是不可否认和不可避免的现状~企业本来就是为了盈利的生产,不可能一直有技术上的挑战。为了生存,更多的时候我们可能会在平庸的企业中度过。但是,能否在平庸的业务中开发出更高效便捷的方式,或许这就是一个突破口,一个亮点。虽然你可能不认同,但这确实是一个让我的开发效率提升了一大半的方案,而且我出门面试的时候也把这个放在了第一点。可能不是特别亮眼和完美,但对我个人来说,从设计-实现-优化的每一步都有自己的思考和落地,对我来说,就是亮点。最后,希望能帮到有需要的人:fire:,大家可以早点下班,给自己多留点时间享受生活!