useForm
useField
和useFields
适合于简单的表单场景,但是对于复杂的表单场景,我们需要更多的功能,比如表单校验,表单提交等。
则可以选用useFrom
,其提供了更加完整的创建可绑定表单的完整解决方案,可以让更方便将AutoStore
的状态和表单控件进行双向绑定,使得收集数据变得更简单。
基本原理
useForm
的基本原理如下:
1. 创建Form
组件
useForm
返回一个Form
组件,该组件是对标准form
的封装。
2. 初始化表单
useForm
内部的useEffect
会自动初始化表单.
然后在初始化时,调用querySelectorAll
获取到所有表单内部的input
,textarea
,select
元素
,依次遍历这些元素,根据name
属性,从state
中获取对应的值,并设置绑定到表单元素上,完成表单字段的初始化。
3. 订阅变更事件
要实现双向绑定,我们需要:
- 监听表单元素的
change
事件
由于表单事件onchange
会冒泡,所以我们只需要在form
元素上监听change
事件即可。
所以通过form.addEventListener('input',onChange)
就可以在表单元素变化时触发捕获到onChange
事件。
然后在onChange
事件中,我们可以通过event.target
获取到表单元素.
最后将表单元素的值更新到state[event.target.name]
。
- 监听
state
的变化
使用store.watch
监听state
的变化,当state
变化时,将数据更新到name=<path>
的表单元素上即可。
快速入门
第1步: 创建表单
首先,我们需要使用useForm
创建一个表单。
import { useForm } from "@autostorejs/react"
const { Form } = useForm({
user:{
firstName:"Zhang",
lastName:"Fisher",
age:18,
vip:false
}
})
useForm
内部调用createStore
来创建一个AutoStore
,所以其本质上useForm
是一个useStore
超集。所以useForm
返回的对象中包含了useStore
返回的对象。tsxconst { useReactive,watch,$,.... } = useForm({...})
useForm
返回值中最主要的是Form
组件,该组件是对标准form
的封装。
第2步:声明字段
声明字段有3
种方式:
简单字段
只需要为input
、textarea
、select
元素设置name
属性,且name
为一个字符串路径
指向状态即可。
<input name="user.firstName" />
封装字段
当然,实际中的输入字段我们一般会进行封装,以便可以进行更多的控制。
我们也可以在封装元素上通过data-field-name='<状态路径>'
标识这是一个表单字段。
<div data-field-name="user.name" >
<label>First Name</label>
<input/>
<span className="invalid"></span>
</div>
- 使用
data-field-name
标识表单字段可以让表单能进行更多的控制。
Field字段组件
使用Field
字段组件可以实现更复杂的控制,如校验、字段联动等等
const { Form,Field } = useForm({...})
<Field
name="user.name"
validate={(value)=>value.length>=3}
render={({value,validate,onChange,name,error})=>{
return <>
<label>First Name</label>
<input name={name} value={value} onChange={onChange}/>
<span className="invalid"></span>
</>
}}
>
</Field>
第3步:提交表单
基本用法
useForm
返回一个Form
组件,该组件是对标准form
元素的封装。
import { useForm } from "@autostorejs/react"
const { Form } = useForm({
user:{
firstName:"Zhang",
lastName:"Fisher",
age:18,
vip:false
}
})
<Form>
<input name="user.firstName" />
<input name="user.lastName" />
<input name="user.age" />
<input name="user.vip" />
</Form>
就这么简单,轻松实现表单
与store.state
之间的双向绑定了,输入的数据会自动同步到state
中,反之亦然。
下面是一个简单的示例:
提示
配置input
元素的name=<状态数据路径>
即可。
表单校验
标准校验
当指定customReport=false
时使用标准的HTML
表单校验功能,只需要在input
元素上设置maxLength
、minLength
、required
、pattern
等属性即可。
- 当表单校验失败时,表单会提供伪类
:invalid
,可以通过input:invalid
、form:invalid
来设置样式。 - 关于更多的
HTML5
表单校验功能请参考:HTML5 表单验证 - 关于
input
元素的属性请参考:input
自定义校验显示
多数情况下,我们并不满足于默认的校验显示方式,我们希望自定义校验显示,比如在输入控件下方显示红色错误提示。
customReport=true
是默认情况- 使用
data-field-name="xxxx"
指定字段名。 - 重点:由
data-validate-field="<字段状态路径>"
指定该元素要显示哪个字段的校验错误信息。
提示
- ❓ : 为什么
user.name
在初始化时不会显示校验错误? - 🍨 : 上例中
user.name='x'
不满足minLength=3
校验规则,但是在初始化时并没有显示错误。这是浏览器的默认行为,必须是有用户输入时才会校验。并且这种行为在不同的字段校验规则却是不一样的,pattern
校验就会在初始化时生效。上例中的phone
就会显示错误。这种不一致性主要是由浏览器决定的,不同的浏览器还不一样,要避免这种不致性,需要使用自定义校验规则。
自定义校验规则
上述两个例子中,我们使用的是标准的HTML5
表单校验规则功能(pattern
、minLength
等等),这存在几个问题
- 校验规则比较单一,无法满足复杂的校验需求。
- 不同浏览器的校验行为存在细微差异。
为此,我们需要在使用useForm
时,传入一个validate
函数,该函数用于自定义校验规则,实现更复杂的校验逻辑。
validate
函数的签名如下:
{
validate?:(path:string,value:any,part:string | null,fieldEle:HTMLElement)=>boolean | string
}
参数 | 说明 |
---|---|
path | 字段元素的name 属性,即状态路径 |
value | 字段值 |
part | 将字段值分解为几部分时的标识,详见下文分解字段 |
fieldEle | 字段元素 |
validate
函数返回true
代表校验成功,返回false
代表校验失败。返回string
代表校验失败时的提示信息。- 当校验失败时,会在
field
元素,input
元素以信data-field-name
元素自动添加invalid
类。
提示
对比上例浏览器的默认行为,user.name
在初始化时会显示校验错误
校验样式控制
拆分字段
在input
元素上 data-field-part
属性可以指定字段的分割方式,将一个状态值绑定到多个input
上,实现双向绑定。
正则表达式拆分
当状态值是一个字符串时,可以指定data-field-part="<正则表达式>"
进行拆分。
下例中net.ip
是一个ip
地址,我们希望将其拆分为4
个input
元素进行绑定。
<div data-field-name="net.ip">
<div>
<Input data-field-part="(\d{1,3})\.\d{1,3}\.\d{1,3}\.\d{1,3}" inline width={60} />
<span>.</span>
<Input data-field-part="\d{1,3}\.(\d{1,3})\.\d{1,3}\.\d{1,3}" inline width={60}/>
<span>.</span>
<Input data-field-part="\d{1,3}\.\d{1,3}\.(\d{1,3})\.\d{1,3}" inline width={60}/>
<span>.</span>
<Input data-field-part="\d{1,3}\.\d{1,3}\.\d{1,3}\.(\d{1,3})" inline width={60}/>
</div>
</div>
data-field-part="<正则表达式>"
只适用于指向的状态值是字符串的情况。- 由于状态与
input
是双向绑定的,而<正则表达式>
不仅用于从状态中提取,也用于将输入input
的值更新到状态的指定位置中。 - 我们约定,
<正则表达式>
中必须具有一个捕获组,如:\d{1,3}\.(\d{1,3})\.\d{1,3}\.\d{1,3}
,其中(\d{1,3})
就是非命名捕获组。在绑定时会读取该组的值,更新到input
状态中,反之同理。 - 正常情况下,每一个
part
均具有不同位置的正则捕获组,所以part
的值是不同的。如果没有正确的指定捕获组,则会导致字段拆分不能正常工作。
提示
基于正则表达式的拆分是一种非常灵活的方式,适用于字符串内容,要求开发者设计可双向兼容的正则表达式。
数组拆分
当状态值是一个数组时,可以指定data-field-part="<索引>"
进行快速拆分。
提示
数组拆分是一种快速的拆分方式,适用于数组内容,拆分时会识别数据类型,自动转换为对应的数据类型。
对象拆分
当状态值是一个{...}
时,可以指定data-field-part="<Key>"
进行快速拆分。
自定义拆分
如果上述的拆分方式不能满足需求,也可以通过自定义toState
和fromState
这两个配置参数来自定义拆分方式。
toState
: 用于将input
的输入值转换为状态值。fromState
: 用于将状态值转换为input
的输入值。
注意
如果指定了toState
、fromState
,则需要开发者自行处理数组和对象的拆分逻辑。
表单字段
Field组件
Field
组件是useForm
提供的一个用于封装表单字段的高级组件,可以用于更加灵活的表单字段封装。
Field
组件的基本用法如下:
const { Field, Form } = useForm({...})
<Form onSubmit={}>
<Field
name="user.firstName" // 使用绑定的状态路径
validate={validate} // 自定义校验规则
render={({value,timeout,....,bind})=>{
// 在此渲染表单字段
return <div>
<label>First Name</label>
<input {...bind} />
</div>
}}
/>
</Form>
提交表单
useForm
提供了submit
方法,用于提交表单。
submit
方法会触发submit
事件,可以通过onSubmit
监听该事件。- 然后在
onSubmit
事件中,使用AJAX/fetch
将表单数据提交到服务器即可。 submiting
属性用于标识表单是否正在提交中,可以用于控制提交按钮的状态。
示例如下: