在开发表单类的组件库时,我们常常需要将input
这类组件变成可配置化,这样其他开发人员使用起来只需要输入某些特定的参数便可使用。这里我介绍的是将这类组件做一个全面而专业的封装,复杂程度不亚于Ant Design,如果你的项目只需要一个简单的公共组件,参考其中你觉得有用的部分就够了。
首先我们参考MDN和实际工作中的应用,将这类组件分个类:
- text:最常见的单行输入栏,包括:
text
search
url
tel
email
password
- textarea:多行输入栏
- number:数字类的输入栏,包括:
number
currency
- select:单选,多选
select
cascader
treeSelect
- autocomplete:单选,多选,还可以异步,一边输入一边加载
- radios:由多个radio组成,多选一
- checkboxes:由多个checkbox组成,可多选
- color:选择颜色
- datetime:时间类输入栏,包括:
date
datetime-local
datetime
month
time
week
range
- file/upload:文件上传,有的有可拖拽区域,有的有上传文件预览
- switch/checkbox:boolean状态选择或切换
- rating:五星评分
- slider:数字滑块,可以有一个或多个数字
- chips:标签输入框
那么在开发这类组件时,我们需要开放哪些参数呢?value
,disabled
,required
(必填)是比较容易想到的,而作为一个公共库开发人员,我们需要想得更远,因为只有我们做的工具能适用在尽可能多的场景,我们的价值才得以发挥。
现代三大主流框架Vue,React,Angular中,只有大而全的Angular自带表单功能(Reactive form),也给了我一个参考,下面我尽可能多的把实际应用中会用到的状态列出来。
value 值
这个不用多说了,业务场景只要稍微复杂一点,我们就需要使用受控组件,或者说是将某个表单字段与对应的值绑定起来。
defaultValue 默认值
这个也容易想到,value是动态的,defaultValue基本上是一开始就会设置好的。当然默认值也可能是动态的,前面某个选项变化也会影响这里的默认值,比如省份选择"湖北",市默认选择"武汉",而省份选择"上海",省份选择"湖南",市默认选择"长沙"。
valid 值是否有效
当用户输入了非法的值时,我们经常看见那个input
会有一个红框,意味着这里的valid
属性为false
。我们也可以用inValid
,但为了统一为正向语义,这里我用valid
。如果下面定义了errors,其实我们可以考虑不再使用valid
,因为errors没有的话就意味着值是有效的。
errors 报错提醒
当用户输入了非法的值时,我们可能会看到一个红色提示,告诉我们为什么错了,比如某个密码设置必须包含数字和大小写。报错可以有多个,所以需要一个数组来存。 每一个error中需要定义的有:
字段 | 解释 |
---|---|
errorType 错误类型 | 在校验器中可以定义校验类型,如果校验为错误,这个类型就是错误类型,比如validEmail 。 |
errorMsg 错误信息 | 具体报错的信息,比如"密码必须包含大小写字母"。 |
errorFieldLabel | 出错的表单组件的label是什么,这个展示给用户,他们就可以快速辨认是哪个组件。 |
errorFieldPath | key值的路径,给开发用来快速找到是哪个字段出了错 |
processing
由于有些组件的状态或属性需要异步的计算,如果还在计算当中,我们都可以用processing
。Angular中叫pending
,这里我用processing
,因为后期说不定会用到processed
。
enabled
我们也常需要定义一个表单组件是否需要禁用,当enabled
为false
时,用户不无法输入/改变它的值。为了实现动态变化,更高层的封装会定义enabledOn
visible 可见或隐藏(保留值)
这个属性可能是动态的,很多情况用hidden
,但为了统一为正向语义,这里我用visible
。hidden
在原生元素中意味着不显示但提交表单时,值会随之存在里面的,所以我需要再定义一种情况是,既不显示也不带值。为了实现动态变化,更高层的封装会定义visibleOn。
available 可见或隐藏(不保留值)
这是个特别的属性,当为true
时就按正常方式运作,为false时不显示,而且在对应form
对象中,这个字段的键值对会被清除掉。比如在一张人员信息登记表单中,当前的组件采集到了年龄,如果当前整个表单值为{ name: "jenny", age: 18 }
,当available
改变为false
时,表单值会随之改为:{ name: "jenny" }
。为了实现动态变化,更高层的封装会定义availableOn。
required 是否为必填项
这个很常用,也可能是动态的,基于前面的用户输入,这里的表单项,可能是必填也可能不是,有的地方用optional
,但为了统一为正向语义,这里我用required
。为了实现动态变化,更高层的封装会定义requiredOn。
touched 用户是否碰过
即使用户点了一下什么都没有输入,马上就blur出去了,这里仍然算touched。比如某些必填项,如果用户什么都没填就点其他的,这个必填项会显示出一个红框并提醒用户这里还没有填。
dirty 值是否被改变过
dirty
表示用户改过这里的值,即使当前value
可能跟defaultValue
是一样的,dirty
也可能为true
。
different 当前值与默认值是否一样
可能用户改过这里的值,但后来又改回原来的默认值的话,这里的状态为{ dirty: true, different: false }
。
label 名字
这个太常见了,如果你要收集姓名,label为"姓名"。
key/name
这个很重要,如果你要收集的是姓名,那么在表单中对应的为name。这个值也作为整个form中的key传到服务端。但由于定义一个表单时key直接用在了定义某个字段了,这里不一定需要再定义了。对于原生表单元素,可以作为name使用。
helperText
贴在组件下面的提示语。
参考MDN上的原生表单元素后,下面我把可能会用到的原生属性也加进来
placeholder 占位文本
某类组件可以用到,比如搜索。
pattern
原生组件中只有当type
为:text
,search
,url
,tel
,email
,和password
时支持使用pattern
,pattern相当于是一个校验器,我们可以增强这里的功能,利用原生校验的功能,将校验结果存到errors中。
再加上简单的样式,布局排列的设置
id, className
开发可以自定义样式
width
开发可以快速定义这个组件的宽度,比如"50%"
。
size
一般来说一整个form的样式是统一的,每个组件的大小也应该是一样的,但不排除一些特例。这里先定义三种:sm
,md
,lg
,实现起来时,会和组件库对接。
tabindex
侧重在无键盘使用时的便利性
title
作为tooltip显示
对于不同类型的表单组件,有不同的参数需要
accept
适用:file
。
options
适用:select
, autocomplete
, treeSelect
, radios
, checkboxes
。
optionDirection
水平还是竖直排列options,适用:radios
, checkboxes
。
multiple, defaultSelectFirst
适用:select
, autocomplete
。
defaultSelectToday
适用:date
, month
, week
, datetime-local
。
allowValueOutOfRange
对于selectable组件,你是否可以设置一个options中没有的值?如果不行的话,当组件的值被设错了,会自动清空。适用:select
, autocomplete
。
max, min, step
适用: number
, date
, month
, week
, time
, datetime-local
, range
。
maxlength, minlength
适用:text
, textarea
, search
, url
, tel
, eamil
, password
。
rows
textarea默认可输入多少行,超出部分会用滚动条。适用:textarea
。
format
比如:'DD MMM YYYY',适用:date
, month
, week
, time
, datetime-local
。
clearable
输入的部分是否可以一键清除,比如输入框右边的叉号。适用:text
, textarea
, search
, url
, tel
, eamil
, password
, file
, select
, autocomplete
, treeSelect
。这里我没有用"reset",因为如果设置了默认值的话,reset是将当前值重置为默认值,而用"clear"就很清晰意味着清空。
resettable
reset不同于clear,clear清晰地意味着清空,对于没有默认值的组件reset和clear起到的效果一样,但设置了默认值的话,reset是将当前值重置为默认值。适用:text
, textarea
, search
, url
, tel
, eamil
, password
, file
, select
, autocomplete
, treeSelect
。
deletable
意味着该组件对应的字段可以被删除,适用于每一个表单组件。
mention
对于输入框,用户可以mention其他人,这个功能光靠这一个字段估计还不够,后期需要具体再做设计。
validations
适用于每一个表单组件,校验器比较复杂,这里不再赘述,后期具体再设计。
不知不觉写了这么多,其实想到的还有好多,比如options也有好多种,tree还是级联?tree的父节点是否可以选?options的分组如何设置?textarea的字数限制怎么设置?校验器也是个重头戏,普通校验和异步校验分别该怎么设置?等等。很明显这个组件不是一次封装就可以完成的,不同的状态和属性属于不同层级的封装,比如visibleOn,这个是给外层开发用来定义什么情况下这个组件显示,而visible才是底层组件真正的状态之一。下面我们来设计封装的层级:
我们分两个层级封装,一个更高阶,面向使用者,一个更底层,面向组件本身。不同的属性和状态要么更面向用户(这里是开发者),要么更面向底层组件。
为了能让封装好的表单组件适用于不同的组件库,比如MUI,PrimeNG,AntDesgin,SemiDesgin等,我们可以额外做adaptor来适配,adaptor加上以上的两层封装足以全面地满足业务需求了。
参考
angular.io/api/forms/A...
developer.mozilla.org/en-US/docs/...