目录
前言
表单是前端开发中常见的场景,注册登录、调查问卷,乃至租赁一个云服务器都是在填写表单。
本文根据我的表单开发经验分享表单开发中,为什么需要表单库进行辅助。
原生表单
前端技术上,表单对应form标签,MDN有详细的介绍。
以一个注册表单为例,我们需要如下标签
这离用户体验还很远,想要完善它的功能,都要依靠JS,原生表单有很多能力缺口,例如:
- 无法展示错误信息
为了告知用户错误的原因,常见在输入框底部进行红色文案的提示,如下图。HTML会给校验失败的元素添加CSS 伪类 :invalid
,但这还远远不够。
- 不支持自定义校验
内置支持require、pattern正则这些能力,但确认密码的校验需要和第一次填写的密码进行对比,没有相关的属性可以用。
React中的表单
尝试在React下实现一个完整的注册表单。
我们维护了两个状态对象,一个formValue代表表单值,一个error用于渲染错误信息。每一个input输入框传入value与onChange使其受控,用户输入后会修改formValue并进行校验。同时提交时也会重新进行校验,满足条件会打印数据。
表单库
如果借助表单库会更简单么?使用React Hook Form,代码如下
可以看到只用了不到一半的代码实现了相同的功能。
表单库都做了哪些事情
从上面一个简单的例子可以看出表单库可以大大简化代码的编写,上述例子主要体现状态管理(formState和error)以及校验,下面将更体系介绍一个表单库可以提供的能力。
一、状态管理
- 表单值集中管理
表单在提交时需要上传一个对象,如
{ "name": "pureink", "password": "fakePassword", "confirm_password": "fakePassword",}
在上面的例子中,我们创建了一个formState状态,为每一个输入框绑定了值,同时在onChange时修改formState,表单库将接管这些状态的管理。
我们可以以一个整体对该数据进行处理,例如可以给表单赋予初始值
const { register, getFieldState, formState: { errors } } = useForm({ defaultValues: { name: "张三", }, })
- 辅助状态管理
表单中最重要的状态是值,但我们还需要其他状态辅助渲染
例如:
touched:一个全新的表单,在用户填写前,即使输入框校验为空,也不应该展示任何错误消息,touched可以帮助我们判断用户是否有输入操作
isValidating:对于异步校验的表单,你可能希望在校验时禁止用户的输入
error:校验错误的信息,例如渲染在输入框下的红色文案
类似的属性还有active、dirty、visited等等,对于每一个表单项,表单库都会管理以上状态。
- 值清空和重置
问卷中常见B问题只在A问题选中某一项时展示,对于会显示和隐藏的表单项,表单库可以处理其值的清空和重置。
下面例子中,勾选问题1后,填写问题2。此时取消勾选问题1并提交,将不会看到问题2的值。
(也支持不清空,例子中可以设置shouldUnregister为false)
二、校验
- 自定义校验
不受限于HTML的required和pattern,你可以编写任意的函数进行同步或异步的校验。
- 校验策略
支持表单级别校验(Form-Level),字段级别校验(Field-Level)
所有表单库都支持字段级别校验,你可以对每个字段指定校验规则,细粒度的进行控制
部分表单库提供表单级别校验,对于简单表单,可以简化校验的编写,如Final-Form
<Form onSubmit={onSubmit} validate={values => { const errors = {} if (!values.username) { errors.username = 'Required' } if (!values.password) { errors.password = 'Required' } if (!values.confirm) { errors.confirm = 'Required' } else if (values.confirm !== values.password) { errors.confirm = 'Must match' } return errors }}>xxx</Form>
- 校验集成
如React-Hook-Form、Formik可以与Yup、Zod这样的校验规则库结合使用
const schema = yup .object({ firstName: yup.string().required(), age: yup.number().positive().integer().required(), }) .required()
- 校验时机
触发校验的时机不只是onChange, 例如注册表单中你可能需要发起请求判断用户名称是否重复,使用onChange可能会校验多次,可以选择blur(失焦)事件触发时校验。
例如Final-Form和Formik都支持validateOnBlur参数。
- 主动触发
例如注册表单中,输入了同样的密码和确认密码,此时如果再次更改密码,确认密码的校验并不会运行,因为并没有对它进行直接的更改,触发其onChange事件。
表单库支持主动触发一个字段的校验,如React Hook Form、Formik
Final-Form使用了另一种方式支持这种场景,本文不进行深入。
- 提交联动
表单进行提交时,需要对每一项内容进行校验,一些表单库还会提供滚动、focus到第一个错误表单项的能力。
- 渲染
一些表单库如Formik可以和第三方组件库如Antd简单结合,错误时输入框会变红等。
三、联动
多字段表单中各个字段经常互相关联,例如选择地区时,需要根据省市区的顺序进行选择,前一个选择影响后面的可选项。
你可以选择自定义onChange方法,当值变更时进行操作,也可以使用useEffect触发。
像Formily这样的库支持一对一、一对多、多对一等方式的联动处理,开发效率和维护性都更高。
四、其他
- 性能
使用React Context管理表单值会导致任何一个表单项值变化,整个表单都会重新渲染一遍。表单库提供监听的功能,避免重复的渲染。
如Formik的FastField,Final Form的subscription
- 表单提交处理
除了可以直接拿到整个表单的值、触发整体校验以外,你可以感知表单是否在提交展示Loading动画,或禁用输入框避免用户此时输入等。
- 数组或嵌套字段
对于复杂字段的处理,如Final Form对于数组能力的支持
import { createForm } from 'final-form'import arrayMutators from 'final-form-arrays'
// Create Formconst form = createForm({ mutators: { ...arrayMutators }, onSubmit})
// pushform.mutators.push('customers', { firstName: '', lastName: '' })
// popconst customer = form.mutators.pop('customers')
- 动态化配置
如Formily这样完备的库支持JSON Shcema形式配置表单。
总结
表单因为多字段下状态管理和校验复杂导致代码量很多,借助表单库可以提升很多效率。
了解一个库的设计目的才能更好的使用工具,本文旨在说明为什么需要一个表单库 / 表单库做了什么事情。
下一篇文章会对React生态下流行的表单库进行多维度对比。