首发于 百姓网前端专栏
前端表单进阶之路:通过 Vue.js 实现表单可配置化

前端表单进阶之路:通过 Vue.js 实现表单可配置化

表单开发是 Web 开发中最常见的需求之一,表单本身的复杂度也在日益增加。我们如何借助技术手段,更好地实现表单结构、组织业务代码?本文介绍了使用 Vue.js 构造可配置化表单的一些经验。

背景

作为现代网页中最早具有逻辑的部分,表单至今仍在博客类、分类信息以及论坛等以用户发布的信息为核心的网站中,扮演着重要的角色。对这些网站来说,表单意味着信息的初始来源,因此它实际上承载了对于信息处理的第一手逻辑。对于不同的类目,表单的内容显然在业务上需要进行区分,所以,如何实现表单内容的区别化和可配置化就成为了这一类 Web 应用的一大重点。

传统的 Web 应用使用服务端直接输出表单的方式,来针对不同的页面逻辑输出不同的表单内容。一些相对完备的框架会提供服务端通过一些简单的配置输出表单的功能。例如,PHP 框架 Laravel 提供了通过 Form::textarea('content', null, ['class' => 'form-control']) 这样的方式来允许在视图的模板层渲染一个表单控件。然而,在交互逻辑日益复杂的今天,许多需求,例如:字段的实时校验、控件之间的联动,在这种模式下的实现是非常困难的,简单的服务端渲染已经远远不能满足业务的发展需求。

微软的 WPF 最早向我们展示了应用的 MVVM 模式,而 Knockout 则将它带入了前端的世界。到目前,以 React 和 Vue 为代表的视图层框架已经很好地将这种模式投入了生产中。而本文将要介绍的,则正是通过 Vue.js 框架来优化我们的表单开发能力和体验。

目标

抛开技术领域的探索,对于表单,我们要达成的目标是什么呢?

试想,有这样的一些需求:

  1. 一个最简单的表单中,需要有内容、地点、联系方式三个字段
  2. 内容字段至少需要填写 8 个字,且不能包含一些简单的违禁词组
  3. 地点字段是一个树形的选择控件,需要提供给用户从省级选到区县级的能力
  4. 联系方式是必填的,并且这个字段必须是手机号码
  5. 如果内容字段中出现了手机号码,且用户没有填写号码,需要将这个号码自动补充到联系方式中

大家看,即使是内容如此简单的表单,也会有这样的需求。有一些功能,例如:必填、格式校验,我们可以通过 HTML5 中的 required 或者 pattern 这样的字段来实现原生的约束,而更多复杂的功能则必须交由 JavaScript。抛开这一部分不谈,在纯页面结构上,我们想要的大概是这样:

而我们期望能有这样的配置直接配置上述的页面结构,以及其部分的逻辑:

再加上一点简单的业务逻辑代码,就构成了我们对于表单的全部配置,而剩下的工作都由表单框架来生成。

实现

关于如何使用 Vue.js 搭建一个简单的 Web 应用,在很多地方已经有非常优秀的介绍,例如 Vue.js 的官网 [1] 就提供了很多实例,因此我们也不再赘述。在这里我将只介绍一些核心的实现,以供大家参考。

基本的实现逻辑如下图所示:

整个流程可以分为:后端数据传递(品红)和外部扩展(蓝色)两部分,接下来会对各个部分的核心流程详细介绍。

后端数据传递

Vue.js 面向的运行环境在绝大多数的手机浏览器上是可以良好支持的 [2] 。因此我们可以直接在 HTML 或者对应的模板文件中写如下的代码:

(注:这里使用的语言是 Blade [3])

#my-form 这个元素作为我们交由 Vue 控制的根容器声明,而 <my-form> 则是我们为表单创建的控件。这里值得注意的是,我们通过一个带有 ref 的 script 标签来使得我们可以从后端传递数据给 Vue 组件。

在这里,我使用了两个来自于后端的数据对象。schema 是类似于上一节中我提到的配置内容,它将通过 Vue 的根容器传递给对应的表单控件;而 context 则用于处理其他需要后端读取的数据,例如一些代码中可能会根据不同的用户角色进行处理,则我们可以把这部分信息也传递给 JS 便于控制。

在 JS 文件中,我们可以使用如下的方式来处理上述的数据:

这样,我们就可以通过实现form.vue来实现我们的表单构造。

附注

  1. http://vuejs.org/v2/examples
  2. caniuse.com/#
  3. laravel.com/docs/5.4/bl

构造表单控件

在 my-form 组件中,我们可以通过后端传递的 Schema 配置,来生成对应的控件

my-line 这个元素,在这里被我们用于构造统一的表单模板,例如,所有的控件都会被 <div class="form-line"></div> 这样的容器包裹,那么我们可以将这部分内容作为 my-line 元素的模板声明。使用这种方法我们可以构造相同的 Label 元素、错误提示等。

在 my-line 组件中,我们可以通过这样的方式来声明实际的表单控件:

这种方式看起来简单直接,但它会使 my-line 组件变得异常复杂。为了解决这个问题,我们可以引入一个虚拟组件 my-control,由它自己根据不同的 schema.type 渲染出不同的表单元素。

Vue.js 中使用函数式组件可以声明一个本身不渲染,但可以调用子组件的组件。我们只需要这样声明:

这样,可以将控件的复杂度从my-line这个组件中抽离出来,更有利于各组件的独立维护。

控件继承

如上所述,我们已经可以将各种控件,例如 my-input、my-textarea 独立进行实现。但是,这些组件中可能会有一些通用的逻辑。比如,控件对应的表单字段显示的名称,我们实际上需要这样的属性:

再比如,我们对于所有的控件,都会有对应数据的 data 属性;或者对于各个组件,我们需要统一执行生命周期方法对应的操作。这种情况下,我们可以将统一的实现抽象为一个独立的类:

并且,由于 Vue 的 mixin 机制,我们可以在 contract.js 中声明统一的生命周期函数,而在控件对应的组件中,再次声明生命周期函数不会覆盖统一的处理,而是会在统一函数之后执行。这保证了我们可以安全声明独立的生命周期而无需再次添加统一逻辑。

外部元素

有一些比较特别的元素,例如:提交按钮、及有些网站发布表单可能会出现的协议勾选,这些东西显然不能作为表单控件注入。但我们可以使用其他方式来简单实现:

通过 Slot 机制,我们可以从外部向 Form 内注入一个不属于表单控件的元素。同理,如果我们需要加入一些 CSRF 元素等隐藏的表单项,也可以通过这种方式进行。

扩展

在完成了基础组件之后,我们还有一些基本的交互功能,以及业务逻辑可能会考虑的功能。例如上文中提到的必填等。这时候,我们需要从 JavaScript 角度对我们的表单进行扩展。

为了防止业务逻辑扩散到控件逻辑中,我们需要提供一套机制来使得业务逻辑可以在对应的时刻执行。例如,必填的真实含义其实是当控件数据改变时,观察是否为空。如果存在必填项数据为空,禁用提交按钮。显然,控件数据改变时是生命周期的一个过程(updated,或者是自定义的 @change 事件),所以我们可以通过事件传递的机制来实现一套业务逻辑处理的框架。

表单的核心是 Form(表单元素)和 Control(控件),所以,我们需要通过一个独立的 Event Emitter 将对应的核心控件的事件代理出来。

通过这种方式,我们可以通过 Core.form() 或者诸如 Core.control('content') 的方式来获得一个在当前页面持久有效的 Emitter。然后我们只需要在对应的 Vue 文件中代理生命周期事件:

为了避免全局引入 CoreProxy,可以把这个类暴露在 Vue.prototype 上。通过 Vue Plugin,可以实现下面的效果:

通过这种方式,我们可以将对应的 Vue 对象传递给 Core 来代理,但同时不把它直接暴露给外部。比如我们的代码可能是这样:

同理,我们也可以将事件在不同的组件中传递。例如,我们需要在“类型”选择为“手机号码”的情况下校验“联系方式”字段

因为在 Core 的内部,我们可以获取到对应的 Vue 对象,所以我们完全可以暴露出一些类似于 read 这样的只读方法供外部调用;对于数据修改,例如外部也可能需要修改其他控件的数据,我们同样可以提供一些内置的事件,例如 Core.control('contact').emit('write', newValue) 来使得外部有能力修改这些数据,同时可以得到统一的控制。

总结

以终为始,我们最后来聊一聊,为什么我们的表单在 Vue 这样的框架中可以被更好地表达:

  1. 双向绑定机制。双向绑定意味着我们无需关心数据的变化对视图的重绘,也不用关心用户操作如何同步到 JS 数据的修改,这使得我们对于数据的处理可以被高度简化;同时,Vue 对数据同步给出了非常多的选项,例如 .lazy、.trim 等修饰符,可以让我们将精力集中在对于逻辑本身的处理上
  2. 组件化。表单本身是一个非常适合使用组件化的场景,因为每一种表单控件都表现出了共性和差异性,而表单本身就是由各种各样的控件构成的。将控件抽象为组件,可以说是一种必然,也是处理控件的最佳方案
  3. 模板描述。Vue 使用模板来描述如何渲染一个组件,由于 HTML 本身的表单控件就是大量逻辑的封装,因此,比起渲染函数,使用模板来描述一个表单控件是直接而自然的。使用 Vue 官方的语言来解释,比起偏逻辑(logical)的组件而言,表单控件本身其实是偏视图的(presentational),因此模板显然会有更出色的表现

Vue.js 本身是一个非常优秀的框架,一方面,它可以通过最精简的方式让我们以 Vue 组件的形式描述出我们的控件;同时,我们可以使用 Vue 提供的一系列其他功能,来实现诸如控件抽象、控件分发、事件传递模块的共用、外部内容注入等更加复杂的功能。如果大家在平时也有类似的表单开发的需求,不妨尝试使用 Vue 来构建。


作者:孙翛然
简介:前端工程师、Web 开发工程师,致力于基于不同框架和语言的业务架构设计和开发。

本文仅为作者个人观点,不代表百姓网立场。
题图来源:Pixabay

本文在 “百姓网技术团队” 微信公众号首发, 点此扫码订阅。

天下网标王唐山推广网站哪家好本溪网站建设设计价格林芝营销型网站建设昌吉网络推广沈阳网络广告推广哪家好大鹏网页设计价格广东网站优化推广报价嘉兴网站建设设计报价玉树网站优化排名多少钱清远高端网站设计忻州英文网站建设报价张家口模板制作哪家好濮阳SEO按天收费推荐南通阿里店铺运营公司海南企业网站建设多少钱伊犁网站改版多少钱文山网站优化按天收费哪家好阿坝企业网站制作公司绥化网站制作怒江关键词排名包年推广价格四平关键词按天扣费郑州建设网站多少钱鞍山网站优化软件多少钱信阳网站优化按天扣费滁州网站推广方案推荐四平优化巢湖网络营销推荐台州网络推广公司福田关键词排名黔南模板制作香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声卫健委通报少年有偿捐血浆16次猝死汪小菲曝离婚始末何赛飞追着代拍打雅江山火三名扑火人员牺牲系谣言男子被猫抓伤后确诊“猫抓病”周杰伦一审败诉网易中国拥有亿元资产的家庭达13.3万户315晚会后胖东来又人满为患了高校汽车撞人致3死16伤 司机系学生张家界的山上“长”满了韩国人?张立群任西安交通大学校长手机成瘾是影响睡眠质量重要因素网友洛杉矶偶遇贾玲“重生之我在北大当嫡校长”单亲妈妈陷入热恋 14岁儿子报警倪萍分享减重40斤方法杨倩无缘巴黎奥运考生莫言也上北大硕士复试名单了许家印被限制高消费奥巴马现身唐宁街 黑色着装引猜测专访95后高颜值猪保姆男孩8年未见母亲被告知被遗忘七年后宇文玥被薅头发捞上岸郑州一火锅店爆改成麻辣烫店西双版纳热带植物园回应蜉蝣大爆发沉迷短剧的人就像掉进了杀猪盘当地回应沈阳致3死车祸车主疑毒驾开除党籍5年后 原水城县长再被查凯特王妃现身!外出购物视频曝光初中生遭15人围殴自卫刺伤3人判无罪事业单位女子向同事水杯投不明物质男子被流浪猫绊倒 投喂者赔24万外国人感慨凌晨的中国很安全路边卖淀粉肠阿姨主动出示声明书胖东来员工每周单休无小长假王树国卸任西安交大校长 师生送别小米汽车超级工厂正式揭幕黑马情侣提车了妈妈回应孩子在校撞护栏坠楼校方回应护栏损坏小学生课间坠楼房客欠租失踪 房东直发愁专家建议不必谈骨泥色变老人退休金被冒领16年 金额超20万西藏招商引资投资者子女可当地高考特朗普无法缴纳4.54亿美元罚金浙江一高校内汽车冲撞行人 多人受伤

天下网标王 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化