如何在Jetpack Compose中轻松的进行表单验证

写在前面

本文中提及的 use开头的函数,都出自与我的 ComposeHooks 项目,它提供了一系列 React Hooks 风格的状态封装函数,可以帮你更好的使用 Compose,无需关心复杂的状态管理,专注于业务与 UI 组件。

这是系列文章的第 10 篇,前文:

表单验证的痛点

在开发中,表单验证是一个非常常见的需求,但在 Compose 中实现表单验证却不那么简单。传统的做法通常是:

  1. 为每个表单项创建单独的状态
  2. 为每个表单项编写验证逻辑
  3. 手动跟踪整个表单的验证状态
  4. 在提交时再次验证所有字段

这种方式不仅代码冗长,而且容易出错,特别是在表单项较多的情况下。如果你曾经尝试过在 Compose 中实现一个复杂的表单,你一定深有体会。

那么,有没有更简单、更优雅的方式来处理表单验证呢?答案是肯定的!今天我要介绍的是 ComposeHooks 中的useForm钩子,它可以帮助你轻松实现表单验证。

使用 useForm 进行表单验证

基本用法

首先,让我们看一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Composable
fun SimpleFormExample() {
val form = Form.useForm()

Surface {
Form(form) {
FormItem<String>(name = "name") { (state, validate, msgs) ->
var string by state
Row {
Text(text = "Name:")
OutlinedTextField(value = string ?: "", onValueChange = { string = it })
}
}

// 提交按钮
Row {
Button(onClick = {
if (form.isValidated()) {
val values = form.getAllFields()
// 处理表单提交
println(values)
}
}) {
Text("Submit")
}
}
}
}
}

这个例子展示了useForm的基本用法:

  1. 使用Form.useForm()创建一个表单实例
  2. 使用 Headless 组件 Form 包裹整个表单
  3. 使用 Headless 组件 FormItem定义表单项,通过name属性指定表单项的名称
  4. FormItem 作用域中可以使用 state 状态、validate 校验结果、msgs 校验器提示信息列表

添加验证规则

FormItem组件支持添加多种验证规则,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FormItem<String>(
name = "email", // 表单项名称
Email(), // 验证是否为有效的邮箱格式
Required() // 验证是否为必填项
) { (state, validate, msgs) ->
var string by state
Row {
Text(text = "Email:")
Column {
OutlinedTextField(value = string ?: "", onValueChange = { string = it })
// 显示验证错误信息
Text(text = "$validate ${msgs.joinToString("、")}")
}
}
}

内置验证规则

ComposeHooks 提供了多种内置的验证规则:

  • Required() - 必填项验证
  • Email() - 邮箱格式验证
  • Mobile() - 手机号格式验证
  • Phone() - 电话号码格式验证
  • Regex - 正则表达式验证

自定义验证规则

如果内置的验证规则不满足你的需求,你还可以创建自定义的验证规则:

1
2
3
4
5
6
7
8
9
FormItem<String>(
name = "id",
object : CustomValidator("身份证号码格式错误", {
!it.asBoolean() || (it is String && it.matches(Regex(CHINA_ID_REGEX)))
}) {}
) { (state, validate, msgs) ->
var string by state
// UI代码...
}

表单状态管理

设置表单值

你可以使用setFieldsValue方法一次性设置多个表单项的值:

1
2
3
4
5
6
useMount {
form.setFieldsValue(
"name" to "default",
"mobile" to "111"
)
}

或者使用setFieldValue设置单个表单项的值:

1
2
3
TButton(text = "Set Age to 5") {
form.setFieldValue("age" to 5)
}

重置表单

你可以使用resetFields方法重置表单:

1
2
3
TButton(text = "reset") {
formInstance.resetFields()
}

也可以在重置的同时设置新的值:

1
2
3
4
5
6
7
8
TButton(text = "reset with values") {
formInstance.resetFields(
"name" to "Junerver",
"age" to 5,
"mobile" to "13566667777",
"email" to "composehook@gmail.com"
)
}

监听表单项变化

你可以使用Form.useWatch来监听表单项的变化,并将它的值作为一个 State 状态:

1
2
3
4
5
val form = Form.useForm()
val name by Form.useWatch<String>(fieldName = "name", formInstance = form)

// 在UI中使用
Text(text = "Current name: $name")

获取表单验证状态

你可以使用_isValidated()方法获取表单的验证状态:

1
2
3
4
val canSubmit by form._isValidated()
TButton(text = "submit", enabled = canSubmit) {
// 提交表单
}

在子组件中获取表单实例

当组件位于 Form 作用域下时,在子组件中可以通过 Form.useFormInstance() 获取表单实例

1
val formInstance: FormInstance = Form.useFormInstance()

完整示例

下面是一个更完整的表单示例,包含多种类型的表单项和验证规则:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
@Composable
fun UseFormExample() {
val form = Form.useForm()
useMount {
form.setFieldsValue(
"name" to "default",
"mobile" to "111"
)
}
// 通过 Form.useWatch 可以监听表单项的内容到一个 State
val name by Form.useWatch<String>(fieldName = "name", formInstance = form)

Surface {
ScrollColumn {
Form(form) {
FormItem<String>(name = "name") { (state, validate, msgs) ->
var string by state
ItemRow(title = "name") {
OutlinedTextField(value = string ?: "", onValueChange = { string = it })
}
}
Spacer(modifier = Modifier.height(18.dp))

FormItem<Int>(name = "age", Required()) { (state, validate, msgs) ->
var age by state
ItemRow(title = "* age") {
Row {
TButton(text = "1", enabled = age != 1) {
// 通过 setFieldValue 可以为表单项设置值
form.setFieldValue("age" to 1)
}
TButton(text = "3", enabled = age != 3) {
form.setFieldValue("age" to 3)
}
TButton(text = "5", enabled = age != 5) {
form.setFieldValue("age" to 5)
}
TButton(text = "null", enabled = age != null) {
form.setFieldValue("age" to null)
}
Text(text = "$validate ${msgs.joinToString("、")}")
}
}
}

// 更多表单项...

// 提交按钮
Sub()
}
Text(text = "by use `Form.useWatch(fieldName,formInstance)`can watch a field\nname: $name")
}
}
}

@Composable
private fun FormScope.Sub() {
// 你可以在子组件中使用 Form.useFormInstance() 获取父组件中的 FormInstance
val formInstance: FormInstance = Form.useFormInstance()
// 获得表单是否校验成功的 **状态**
val canSubmit by formInstance._isValidated()
Row {
TButton(text = "submit", enabled = canSubmit) {
println(
formInstance
.getAllFields()
.toString() + "\nisValidated :" + formInstance.isValidated()
)
}
TButton(text = "reset") {
formInstance.resetFields()
}
TButton(text = "reset with values") {
formInstance.resetFields(
"name" to "Junerver",
"age" to 5,
"mobile" to "13566667777",
"email" to "composehook@gmail.com"
)
}
}
}

写在最后

useForm的工作原理是基于状态管理和验证规则的组合。每个FormItem都有自己的状态和验证规则,在 FormItem 作用域中使用其提供的状态,当修改状态时,会通过useEffect 触发验证,并更新验证结果。

表单实例(FormInstance)会跟踪所有表单项的状态和验证结果,并提供方法来获取和操作这些状态。

使用 ComposeHooks 中的useForm钩子,我们可以轻松地在 Jetpack Compose 中实现表单验证:

  1. 无需手动管理每个表单项的状态
  2. 内置多种常用的验证规则
  3. 支持自定义验证规则
  4. 提供丰富的 API 来操作表单状态
  5. 自动跟踪整个表单的验证状态

这种方式不仅减少了代码量,还提高了代码的可读性和可维护性。如果你正在使用 Jetpack Compose 开发表单,不妨试试useForm钩子,它会让你的表单验证变得更加简单和优雅。

探索更多

项目开源地址:junerver/ComposeHooks

MavenCentral:hooks

1
implementation("xyz.junerver.compose:hooks2:2.1.0")

欢迎使用、勘误、pr、star。