微信小程序实现表单校验功能


Posted in Javascript onMarch 30, 2020

小程序SDK版本 1.4

表单校验之难

如果要问微信小程序最难实现的公共业务是什么?应该是表单校验,没有之一。原因如下:

表单组件在数量上达到11个,居各类组件之首。当然幸运的是,并不是所有的都需要校验。
而这些组件操作方式多样,可分为滑动、(多行)输入、点击、点击+滑动。
即使是同一个组件,因为业务场景不同就会有不同的校验规则。
更麻烦的是,这些组件之间经常还会联动或者关联校验。

但是,作为一个非简单静态页面,有着较多用户交互的小程序,表单校验又是一个非常常用的功能:登录、注册、新增、编辑…

总而言之:表单组件的多样性 X 校验规则的多样性 = 复杂的公共业务

这么棘手的问题我们怎么来解决它呢?

尝试组件化

如果你关注近年前端发展趋势,一定会想到“组件化”来实现:

把每个表单组件的视图、样式、校验逻辑封装成单独的业务组件,然后直接调用。

可事情似乎没这么简单。

如果考虑把n个原生组件抽象出来,配上n个校验规则,再乘以组件之间的关系n(的全排列),复杂度至少达到n³。

而且每个组件的校验失败或成功都要通知父组件,以便显示错误信息或者进行下一步操作。

这样不但没有解决问题,反而使得这些公用的表单组件过于复杂,耦合混乱。

尝试非组件化

既然原先的思路行不通,再来回到出发点,看看我们最核心的需要被抽象出来的是什么。

无非是两样东西:视图层的元素样式和逻辑层的校验规则。

上面说到封装原生表单组件会极大的增加复杂度,索性放弃它,复杂度瞬间可以下降到n²。

但同时我们又要保持样式统一,也就是我们常说的风格一致。

比如输入框该多高,错误提示怎么显示,字体大小颜色…之类的。

这个好办,我们把样式类写入一个公共样式文件form.wxss,然后需要的时候引入,甚至可以全局引入。

// form.wxss
.form {
 display: block;
 font-size: 28rpx;
 position: relative;
}
.form-line {
 background-color: #fff;
 border-bottom: 1px solid #e5e5e5;
 font-size: 34rpx;
 height: 96rpx;
 line-height: 96rpx;
 display: flex;
 padding: 0 31rpx;
}
.form-title {
 box-sizing: border-box;
 background-color: #efefef;
 color: #838383;
 font-size: 28rpx;
 padding: 31rpx;
 min-height: 90rpx;
}
...

我们使用的时候只需要在对应的元素上添加相应的样式即可。比如:

// xxx.wxml
<form class="form">
 <view class="form-title">请输入手机号</view>
 <view class="form-line">
 <label class="label">手机</label>
 <view class="form-control">
  <input class="f-1 va-m input" />
 </view>
 </view>
 ...
</form>

那么接下来我们只剩下校验规则和组件关联关系之间这两个难题了。

校验规则理想的状态是可扩展和可配置。

可扩展。随着业务的增长,在不修改已有规则情况可以新增校验规则。

可配置。可单独为每个表单组件配置不同的单个或多个校验规则。

如何做到可定义?用统一的形式即可。比如:

/*
统一的格式:
[规则名]: {
 rule: [校验方式]
 msg: [错误信息]
}
*/
const validators = {
 // 简单的校验用正则
 required: {
 rule: /.+/,
 msg: '必填项不能为空'
 },
 // 复杂的校验用函数
 same: {
 rule (val='', sVal='') {
  return val===this.data[sVal]
 },
 msg: '密码不一致'
 }
 ...
}

如何做到可配置?配置上支持类似数组的形式,然后用统一的函数依次读取这些校验规则,逐个校验。

配置的规则肯定是在原生表单组件上,至于组件的值也只能通过事件对象获取。

如果直接绑定事件进行校验会阻碍父页面获取值,所以最好由父页面绑定事件传值,并且传入事件对象和执行环境进行处理:

/*
校验函数部分代码
e 事件对象
context 页面对象函数执行的上下文环境
*/
let validate = (e, context) => {
 // 从事件对象中获取组件的值
 let value = (e.detail.value || '').trim()
 // 从事件中获取校验规则名称
 let validator = e.currentTarget.dataset.validator ? e.currentTarget.dataset.validator .split(',') : []
 // 遍历规则进行校验
 for (let i = 0; i < validator.length; i++) {
 let ruleName = validator[i].split('=')[0]
 let ruleValue = validator[i].split('=')[1]
 let rule = validators[ruleName].rule || /.*/
 if ('function' === typeof rule) {
  rule.call(context, value, ruleValue) ? '' : validators[ruleName].msg
 } else {
  rule.test(value) ? '' : validators[ruleName].msg
 }
 }
 ...
}

调用起来也非常简单,按照固定的格式加上对应的样式,配置校验规则,然后调用校验函数。

// 部分代码示例
// page.wxml
<form>
 <!-- 一个表单组件 -->
 <view class="form-line">
 <label class="label">授权手机</label>
 <view class="form-control">
  <!-- 校验规则:必须填写,且为电话号码 -->
  <input maxlength="11" class="f-1 va-m input" bindblur="validate" type="number" data-name="phone" data-validator="required,phone" confirm-type="next" value="{{phone}}" />
  <!-- 错误图标 -->
  <icon wx:if="{{form.phone!==undefined}}" type="{{form.phone?'warn':'success'}}" size="16" />
 </view>
 </view>
 ...
</form>
// page.js
valid(e) {
 this.setData({
 [e.currentTarget.dataset.name]: e.detail.value
 })
 validate(e, this)
}

上面的代码中省略了校验错误提示和非空校验。详细代码请查看GitHub仓库:

https://github.com/yalishizhude/miniprogram-seed.git

总结

写代码最然总是要抱着最美好的想法,但同时也要做着最坏的打算。尤其是面对一些底层框架限制的时候。

面对这种情况,我们要从核心需求出发,把能抽出公用的东西都出来,同时保证可配置、可扩展。

好的的架构师不但喜欢未开垦的处女地,也应不惧布满杂石乱草的荒野~

欢迎到评论区留言交流:https://github.com/yalishizhude/webclub-discuss/issues/1

为大家推荐现在关注度比较高的微信小程序教程一篇:《微信小程序开发教程》小编为大家精心整理的,希望喜欢。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Ext JS 4官方文档之三 -- 类体系概述与实践
Dec 16 Javascript
jQuery插件实现屏蔽单个元素使用户无法点击
Apr 12 Javascript
js倒计时小程序
Nov 05 Javascript
jQuery中slideUp()方法用法分析
Dec 24 Javascript
14个有用的Jquery技巧分享
Jan 08 Javascript
基于Flowplayer打造一款免费的WEB视频播放器附源码
Sep 06 Javascript
Jquery中使用show()与hide()方法动画显示和隐藏图片
Oct 08 Javascript
JQuery遍历元素的父辈和祖先的方法
Sep 18 Javascript
angular.js之路由的选择方法
Sep 24 Javascript
前端js弹出框组件使用方法
Aug 24 Javascript
详解create-react-app 自定义 eslint 配置
Jun 07 Javascript
手把手教你如何编译打包video.js
Dec 09 Javascript
Angularjs中ng-repeat的简单实例
Aug 25 #Javascript
微信小程序页面滑动屏幕加载数据效果
Nov 16 #Javascript
Angularjs中数据绑定的实例详解
Aug 25 #Javascript
vue2利用Bus.js如何实现非父子组件通信详解
Aug 25 #Javascript
JavaScript中防止微信浏览器被整体拖动的方法
Aug 25 #Javascript
在Js页面通过POST传递参数跳转到新页面详解
Aug 25 #Javascript
Vue上传组件vue Simple Uploader的用法示例
Aug 25 #Javascript
You might like
一个分页的论坛
2006/10/09 PHP
初步介绍PHP扩展开发经验分享
2012/09/06 PHP
奉献出一个封装的curl函数 便于调用(抓数据专用)
2013/07/22 PHP
PHP 7的一些引人注目的新特性简单介绍
2015/11/08 PHP
学习php设计模式 php实现访问者模式(Visitor)
2015/12/07 PHP
PHP下SSL加密解密、验证、签名方法(很简单)
2020/06/28 PHP
php微信浏览器分享设置以及回调详解
2016/08/01 PHP
PHP-FPM 设置多pool及配置文件重写操作示例
2019/10/02 PHP
超级有用的13个基于jQuery的内容滚动插件和教程
2011/07/31 Javascript
JS时间选择器 兼容IE6,7,8,9
2012/06/26 Javascript
js禁止页面刷新禁止用F5键刷新禁止右键的示例代码
2013/09/23 Javascript
详细解密jsonp跨域请求
2015/04/15 Javascript
浅谈jQuery页面的滚动位置scrollTop、scrollLeft
2015/05/19 Javascript
js自定义select下拉框美化特效
2016/05/12 Javascript
jQuery实现对象转为url参数的方法
2017/01/11 Javascript
bootstrap fileinput 上传插件的基础使用
2017/02/17 Javascript
JS设计模式之状态模式概念与用法分析
2018/02/05 Javascript
Vue结合后台导入导出Excel问题详解
2019/02/19 Javascript
node中使用es6/7/8(支持性与性能)
2019/03/28 Javascript
uniapp与webview之间的相互传值的实现
2020/06/29 Javascript
[00:06]Yes,it worked!小卡尔成功穿越时空加入战场!
2019/07/20 DOTA
python 解析html之BeautifulSoup
2009/07/07 Python
python简单实现旋转图片的方法
2015/05/30 Python
Python 关于反射和类的特殊成员方法
2017/09/14 Python
django数据库自动重连的方法实例
2019/07/21 Python
Pytorch之Variable的用法
2019/12/31 Python
浅谈python的elementtree模块处理中文注意事项
2020/03/06 Python
记一次Django响应超慢的解决过程
2020/09/17 Python
解决python的空格和tab混淆而报错的问题
2021/02/26 Python
北大自主招生自荐信
2013/10/19 职场文书
城管年度个人总结
2015/02/28 职场文书
税务会计岗位职责
2015/04/02 职场文书
《吃水不忘挖井人》教学反思
2016/02/22 职场文书
2016创先争优活动党员公开承诺书
2016/03/24 职场文书
HTML页面滚动时部分内容位置固定不滚动的实现
2021/04/14 HTML / CSS
MySQL 重命名表的操作方法及注意事项
2021/05/21 MySQL