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

写在前面

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

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

表单验证的痛点

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

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

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

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

使用 Form.useForm 进行表单验证

基本用法

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

kotlin 复制代码
@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")
                }
            }
        }
    }
}

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

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

添加验证规则

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

kotlin 复制代码
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 - 正则表达式验证

自定义验证规则

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

kotlin 复制代码
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方法一次性设置多个表单项的值:

kotlin 复制代码
useMount {
    form.setFieldsValue(
        "name" to "default",
        "mobile" to "111"
    )
}

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

kotlin 复制代码
TButton(text = "Set Age to 5") {
    form.setFieldValue("age" to 5)
}

重置表单

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

kotlin 复制代码
TButton(text = "reset") {
    formInstance.resetFields()
}

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

kotlin 复制代码
TButton(text = "reset with values") {
    formInstance.resetFields(
        "name" to "Junerver",
        "age" to 5,
        "mobile" to "13566667777",
        "email" to "[email protected]"
    )
}

监听表单项变化

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

kotlin 复制代码
val form = Form.useForm()
val name by Form.useWatch<String>(fieldName = "name", formInstance = form)

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

获取表单验证状态

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

kotlin 复制代码
val canSubmit by form._isValidated()
TButton(text = "submit", enabled = canSubmit) {
    // 提交表单
}

在子组件中获取表单实例

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

kotlin 复制代码
val formInstance: FormInstance = Form.useFormInstance()

完整示例

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

kotlin 复制代码
@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 "[email protected]"
            )
        }
    }
}

写在最后

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

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

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

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

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

探索更多

项目开源地址:junerver/ComposeHooks

MavenCentral:hooks

kotlin 复制代码
implementation("xyz.junerver.compose:hooks2:2.1.0")

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

相关推荐
Senar5 分钟前
听《富婆KTV》让我学到个新的API
前端·javascript·浏览器
烛阴34 分钟前
提升Web爬虫效率的秘密武器:Puppeteer选择器全攻略
前端·javascript·爬虫
hao_wujing1 小时前
Web 连接和跟踪
服务器·前端·javascript
前端小白从0开始1 小时前
前端基础知识CSS系列 - 04(隐藏页面元素的方式和区别)
前端·css
想不到耶1 小时前
Vue3轮播图组件,当前轮播区域有当前图和左右两边图,两边图各显示一半,支持点击跳转和手动滑动切换
开发语言·前端·javascript
萌萌哒草头将军2 小时前
🚀🚀🚀尤雨溪:Vite 和 JavaScript 工具的未来
前端·vue.js·vuex
Fly-ping2 小时前
【前端】cookie和web stroage(localStorage,sessionStorage)的使用方法及区别
前端
我家媳妇儿萌哒哒3 小时前
el-upload 点击上传按钮前先判断条件满足再弹选择文件框
前端·javascript·vue.js
天天向上10243 小时前
el-tree按照用户勾选的顺序记录节点
前端·javascript·vue.js