表单组件
Flutter 的 Form 组件,ReactNative 中的 RHF 都是体验非常不错的表单组件,特别是 flutter 的。
但是 Compose 一直没有找到合适的表单组件,所以我根据自身需求自定义了一个:
- 首次提交后触发自动校验
- 支持交叉校验(例如密码和确认密码)
- 动态表单(根据条件,隐藏或显示表单项)
如何收集表单项
Compose 中最大的难点在于收集表单项,需要实现 FormField 的注册和反注册。如果参考 RHL 的话通过 DisposeEffect 是很合适的方案。
但是坑爹的地方在于这种方案和 Compose 的路由是不匹配的,因为 Compose 路由是不堆叠组件的,切换页面时即销毁当前页面,页面中的所有组件都会依次执行 dispose。
所以暂时想到的是每次 Form 渲染时重新收集表单:
val LocalFormCollector = compositionLocalOf<MutableList<FieldState<*>>> {
error("No LocalFormCollector found")
}
@Composable
fun Form(
controller: FormController,
content: @Composable () -> Unit
) {
val collectedFields = remember { mutableStateListOf<FieldState<*>>() }
// 每次Form重组时重新收集表单
collectedFields.clear()
Log.d("Form", "Composing Form")
CompositionLocalProvider(LocalFormCollector provides collectedFields) {
content()
}
// 同步字段顺序到 controller
LaunchedEffect(collectedFields) {
snapshotFlow { collectedFields.toList() }
.collectLatest { controller.updateFields(it) }
}
}
@Composable
fun <T> FormField(state: FieldState<T>, content: @Composable FieldRenderScope<T>.() -> Unit) {
val collector = LocalFormCollector.current
if (!collector.contains(state)) collector.add(state)
Log.d("FormField", "Composing FormField:${state.key}")
FieldRenderScope(
value = state.value,
onValueChange = { newValue -> state.value = newValue },
error = state.error
).content()
}
性能优化
其实从性能方面来说能优化的点不多了,额外的优化带来的是使用上的不便。但是最近发现确实有个可以优化的点,对于一个比较大的表单来说可能会有性能上的提升。
FormField 由于状态变化,所以重组是非常频繁的,比如输入框内的内容发生变化都会导致重组。而 collector.contains(state) 这行代码的时间复杂度是 O(n), 对于大型表单来说比较耗费计算资源。
FormCollector 原本只是一个 SnapshotStateList,我们将它改造一下,现在 add 方法的时间复杂度提升到 O(1) 级别
private class FormCollector {
val fields = mutableStateListOf<FieldState<*>>()
private val seen = IdentityHashMap<FieldState<*>, Boolean>()
fun clear() {
fields.clear()
seen.clear()
}
fun add(field: FieldState<*>) {
if (seen.put(field, true) == null) {
fields.add(field)
}
}
}
相应的代码其它部分也要调整,这里有不贴代码了,可以到 GitHub 上直接查看整个项目仓库。
完整参考
我使用自定义的表单组件写了一个简单的 Demo,支持文章开头说的那些特性: https://github.com/aitsuki/ComposeForm