前言
近期开发的移动端项目直接上了 vue3 ,新特性 composition api 确实带来了全新的开发体验.开发者在使用这些特性时可以将高耦合的状态和方法放在一起统一管理,并能视具体情况将高度复用的逻辑代码单独封装起来,这对提升整体代码架构的健壮性很有帮助.
如今新启动的每个移动端项目基本上都包含注册登录模块,本次实践过程中针对登录注册中的表单控件做了一些经验上的总结,通过抽离提取共性代码来提升代码的可维护性和开发效率.
接下来观察一下美工同学提供的图片.
注册页面
登录页面
忘记密码页面
修改密码页面
通过观察上面几张产品图片,可以清晰看出构成整个登录注册模块的核心组件就是 input 输入框.只要把输入框组件开发完备,其他页面直接引用就行了.
输入框开发完了只实现了静态页面的展示,另外我们还要设计一套通用的数据校验方案应用到各个页面中的表单控件.
输入框组件
从上面分析可知,输入框组件是整个登录注册模块的核心内容.我们先看一下输入框组件有哪几种 UI 形态.
形态一
左侧有文字 +86 ,中间是输入框,右侧如果检测到输入框有数据输入显示叉叉图标,如果没有数据为空隐藏图标.
形态二
左侧只有一个输入框,右侧是文案.文案的内容可能是验证码,也可能是点击验证码后显示的倒计时文案.
形态三
左侧依旧只有一个输入框,右侧如果检测到输入框有内容显示叉叉图标,如果内容为空隐藏图标.
布局
依据上面观察而来的现象分析,我们设计这款 input 组件时可以将其分为左中右三部分.左侧可能是文案,也可能是空.中间是一个输入框.右侧可能是文案也可能是叉叉图标.
模板内容如下:
{{ lt }}{{ timerData.content }}
布局上将左中右的父级设置为 display:flex ,子级的三个元素全部设置成 display:inline-block 行内块模式,目的是为了让左侧和右侧依据自身内容自适应宽度,而中间的 input 设置成 flex:1 充满剩余的宽度.
理论上这样的布局是可行的,但实践中发现了问题.
Demo 效果图如下:
右侧持续增加宽度时,中间 input 由于默认宽度的影响导致让右侧向外溢出了,这并不是我们想要的.
解决这个问题的办法很简单,只需要将中间 input 的 width 设置为 0 即可,如下便达到了我们想要的效果.
v-model
外部页面引用上述封装的组件结构如下:
rt="close" placeholder="请输入手机号码" />
外部页面创建了一个表单数据 form_data 如下,但希望能通过 v-model 的形式将 form_data 的数据与子组件输入框的值建立双向数据绑定.
const form_data = reactive({ number_number: '', //用户名 password: '', //密码 ppassword: '', //重复密码 captcha: '', //验证码 })
在 vue3 实现 v-model 非常简便,在父组件中使用 v-model:xx 完成绑定,这里的 xx 对应着子组件要绑定的状态名称,如下所示.
rt="close" placeholder="请输入手机号码" v-model:value="form_data.password" />
接下来子组件里首先声明要绑定的属性 value ,并监听输入框的 oninput事件 .代码如下:
... ...export default defineComponent({ props: { lt:String, rt: String, value: String }, setup(props, context) { const onChange = (e:KeyboardEvent) => { const value = (e.target as HTMLInputElement).value; context.emit("update:value",value); }; return { onChange } } })
oninput事件 的回调函数将获取到的值使用 context.emit("update:value",value) 返回回去.
其中 update:value 里前面部分 update: 为固定写法,后面填写要建立双向绑定的状态名称.如此一来就轻易的完成了 v-model 的绑定.
数据校验
一般来说只要页面上涉及到表单控件(比如输入框),那么就要针对相应的值做数据校验.如果按照原始的方法,当用户点击按钮, js 接受响应依次获取每个表单项的值一一校验.
这样的做法当然可以实现功能,但并不高效和精简.因为很多页面都要做校验,大量的校验逻辑是重复书写的.
我们接下来设计一套通用的校验方案,将那些可以复用的逻辑代码都封装起来,并且能够快速的应用到每个页面上,提升开发效率.
依注册页面为例,模板代码如下.创建四个输入框组件:手机号,手机验证码,密码和确认密码.最后面再放置一个注册按钮.(为了看起来更清晰,下面的代码将所有 ts 类型删除)
在借鉴了一些其他优秀框架的表单实践后,我们首先是在最外层增加了一个组件 Form ,其次给每个输入框组件增加了一个属性 propName .这个属性是配合 rules 一起使用的, rules 是手动定义的校验规则,当它传递给 Form 组件后,子组件(输入框组件)就能通过 propName 属性拿到属于它的校验规则.
整体的实现思路可以从头串联一遍.首先是前端开发者定义好当前页面的校验规则 rules ,并将它传递给 Form 组件. Form 组件接受到后会将校验规则分发给它的每个子组件(输入框组件).子组件拿到校验规则后就能够针对输入框的值做相应的数据校验.
当用户点击注册按钮时,点击事件会获取 Form 组件的实例,并运行它的 validate 方法,此时 Form 组件就会对它旗下的每个子组件做一轮数据校验.一旦所有校验成功了, validate 方法返回 true .存在一个校验没通过, validate 方法就返回 false ,并弹出错误信息.
注册页面逻辑如下:
export default defineComponent({ components: { InputForm, //输入框 Button, //注册按钮 Form, //Form组件 }, setup(props) { const form_data = ...; //省略 const rules = ...; //获取最外层Form组件的实例 const form = ref(null); const onSubmmit = ()=>{ if (!form.value || !form.value.validate()) { return false; } //校验通过了,可以请求注册接口了 } return { form, rules, onSubmmit, form_data }; }, });
定义一个变量 form ,用它来获取 Form 表单的实例.模板上