封装表单元素,应该定义哪些状态或属性?

在开发表单类的组件库时,我们常常需要将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:标签输入框

那么在开发这类组件时,我们需要开放哪些参数呢?valuedisabledrequired(必填)是比较容易想到的,而作为一个公共库开发人员,我们需要想得更远,因为只有我们做的工具能适用在尽可能多的场景,我们的价值才得以发挥。

现代三大主流框架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

我们也常需要定义一个表单组件是否需要禁用,当enabledfalse时,用户不无法输入/改变它的值。为了实现动态变化,更高层的封装会定义enabledOn

visible 可见或隐藏(保留值)

这个属性可能是动态的,很多情况用hidden,但为了统一为正向语义,这里我用visiblehidden在原生元素中意味着不显示但提交表单时,值会随之存在里面的,所以我需要再定义一种情况是,既不显示也不带值。为了实现动态变化,更高层的封装会定义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为:textsearchurltelemail,和password时支持使用pattern,pattern相当于是一个校验器,我们可以增强这里的功能,利用原生校验的功能,将校验结果存到errors中。

再加上简单的样式,布局排列的设置

id, className

开发可以自定义样式

width

开发可以快速定义这个组件的宽度,比如"50%"

size

一般来说一整个form的样式是统一的,每个组件的大小也应该是一样的,但不排除一些特例。这里先定义三种:smmdlg,实现起来时,会和组件库对接。

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才是底层组件真正的状态之一。下面我们来设计封装的层级:

classDiagram Base <|-- Typable Base <|-- Selectable Base <|-- Other Base: +值属性 Base: +控制类状态 Base: +用户行为类状态 Base: +校验类属性 Base: +辅助类属性 Base: +样式类属性 Base: +inputChange() Base: +blur() Base: +change() Base: +clear() Base: +reset() Base: +delete() class Typable{ +输入框属性 +inputValueChange() } class Selectable{ +multiple +optionDirection +optionsLoading +options(选项属性) +getOptions() +select() } class Other{ }
classDiagram Typable <|-- Text Typable <|-- TextArea Typable <|-- Number Typable <|-- Password Typable <|-- DateTime
classDiagram Selectable <|-- Radios Selectable <|-- Checkboxes Selectable <|-- Select Selectable <|-- AutoComplete Selectable <|-- Cascader Selectable <|-- TreeSelect
classDiagram Other <|-- Binary Other <|-- File Other <|-- Range Other <|-- Color Other <|-- Slider Binary <|-- Switch Binary <|-- Checkbox Binary <|-- Radio File <|-- FileInput File <|-- FileDnDArea class Binary{ +labelLeft +labelRight } class File{ +文件类属性 } class Range{ +range类属性 } class Color{ +color类属性 } class Slider{ +滑块类属性 }

我们分两个层级封装,一个更高阶,面向使用者,一个更底层,面向组件本身。不同的属性和状态要么更面向用户(这里是开发者),要么更面向底层组件。

classDiagram 第一层值属性 <|-- 第二层值属性 class 第一层值属性{ +value +inputValue +onChange() +onInputChange() } class 第二层值属性{ +defaultValue +defaultSelectFirst +defaultSelectToday +getDefaultValue() } 第一层控制类状态 <|-- 第二层控制类属性 class 第一层控制类状态{ +enabled +visible +available +required } class 第二层控制类属性{ +enabledOn +visibleOn +availableOn +requiredOn } 第一层用户行为类状态 <|-- 第二层用户行为类属性 class 第一层用户行为类状态{ +touched +dirty +different +onBlur() } class 第二层用户行为类属性{ +clearable +resettable +deletable +mention属性 +onClear() +onReset() +onDelete() +onMention() }
classDiagram 第一层校验类状态 <|-- 第二层校验类属性 class 第一层校验类状态{ +valid +errors +accept +pattern +min +max +step +minlength +maxlength +onError() } class 第二层校验类属性{ +validations +wordCount +onValidate() } 第一层辅助类属性 <|-- 第二层辅助类属性 class 第一层辅助类属性{ +label +key/name +title +helperText +placehoder } class 第二层辅助类属性{ 无 } 第一层样式类属性 <|-- 第二层样式类属性 class 第一层样式类属性{ +className +width +size +tabindex } class 第二层样式类属性{ 无 } 第一层选项类属性 <|-- 第二层选项类属性 class 第一层选项类属性{ +multiple +optionsLoading +isOptionEqualToValue +renderOption +onOptionsChange() } class 第二层选项类属性{ +allowValueOutOfRange +getOptionValueByInput +getOptionLabelByValue +getOptions() }

为了能让封装好的表单组件适用于不同的组件库,比如MUI,PrimeNG,AntDesgin,SemiDesgin等,我们可以额外做adaptor来适配,adaptor加上以上的两层封装足以全面地满足业务需求了。

参考

angular.io/api/forms/A...
developer.mozilla.org/en-US/docs/...

相关推荐
熊的猫17 分钟前
webpack 核心模块 — loader & plugins
前端·javascript·chrome·webpack·前端框架·node.js·ecmascript
GJCTYU1 小时前
阿里云多端低代码开发平台魔笔使用测评
低代码·阿里云·云原生·容器·serverless·云计算
PleaSure乐事13 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
getaxiosluo13 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v13 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript
易云码13 小时前
信息安全建设方案,网络安全等保测评方案,等保技术解决方案,等保总体实施方案(Word原件)
数据库·物联网·安全·web安全·低代码
Oo_Amy_oO21 小时前
【极限编程(XP)】
低代码·极限编程
September_ning21 小时前
React.lazy() 懒加载
前端·react.js·前端框架
晴天飛 雪21 小时前
React 守卫路由
前端框架·reactjs
web行路人21 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架