Jetpack Compose中渲染大量可编辑业务数据的最佳状态更新实践问询
我现在碰到个棘手的问题:手里攥着一大堆业务数据,想在Jetpack Compose里用表格形式渲染出来,还得支持前端修改数据。具体场景是这样的:
我定义了一个User业务类,包含姓名和一个测试用的foo字段:
class User ( var firstName: String, var lastName: String, var foo: Int )
然后我造了个有100条数据的列表,比如全是叫“John Cena”、foo值为0的用户:
val users = List(100) { User("John", "Cena", 0) }
接着我用Compose写了渲染行和表格的组件:
@Composable fun UserRow(user: User) = Row { Text(user.firstName) Text(user.lastName) Text(user.foo.toString()) } @Composable fun UserTable(users: List<User>) = LazyColumn { items(users) { user -> UserRow(user) } }
最后在窗口里直接调用UserTable(users)就能渲染出表格了。
现在想实现前端修改功能——比如点击foo文本让它自增,我给Text加了点击事件:
@Composable fun UserRow(user: User) = Row { Text(user.firstName) Text(user.lastName) Text( text = user.foo.toString(), modifier = Modifier.clickable { user.foo += 1 } ) }
但问题来了:user.foo不是MutableState,修改后界面根本不会自动更新。我琢磨了几个解决方案,但都觉得不够理想:
可能的解决方案
1. 把user.foo改成MutableState
修改User类的定义,把foo改成Compose的状态变量:
class User ( var firstName: String, var lastName: String, foo: Int ) { var foo by mutableStateOf(foo) }
- 优点:不用动其他代码,操作
foo和普通Int一样顺手 - 缺点:
User类属于纯业务逻辑层,把Compose的MutableState塞进来总感觉违和,后端逻辑里还是想用普通Int更纯粹
2. 手动触发重组
给foo的文本组件套个key,用一个状态变量强制触发重组:
@Composable fun UserRow(user: User) = Row { Text(user.firstName) Text(user.lastName) var updater by remember { mutableStateOf(false) } key(updater) { Text( text = user.foo.toString(), modifier = Modifier.clickable { user.foo += 1 updater = !updater } ) } }
- 优点:能完全控制重组时机,不会污染业务逻辑
- 缺点:写法太别扭了,还要手动切换
updater的值,明显不是官方推荐的用法
3. 用单独的状态变量存显示文本
把要显示的foo值放到一个独立的MutableState<String>里:
@Composable fun UserRow(user: User) = Row { Text(user.firstName) Text(user.lastName) var fooText by remember { mutableStateOf(user.foo.toString()) } Text( text = fooText, modifier = Modifier.clickable { user.foo += 1 fooText = user.foo.toString() } ) }
- 优点:UI和业务逻辑的边界划得很清楚
- 缺点:还是得手动更新状态变量,虽然比第二个方案好看点,但总觉得不够优雅
我的困惑
目前我觉得第三个方案相对最优,但始终觉得这不是最干净、最贴合Compose设计意图的做法。想问问大家对这些方案的看法,有没有更合适的解决办法?谢谢啦!
补充说明
很多人建议把User改成数据类,用copy方法修改值。这个思路在示例里可行,但我的实际场景不太一样:
我的User类里有个changeFoo方法,逻辑比单纯赋值复杂得多——要做输入校验、修改其他非原始类型的属性、调用数据库操作等等:
class User ( var firstName: String, var lastName: String, var foo: Int, var someOtherObject: SomeOtherClass ) { fun changeFoo(newFoo: Int) { if (foo > 9999) { someError() } foo = newFoo // 修改其他关联对象 someOtherObject.doSomething() // 额外操作:数据库调用等 doSqlStuff(this) andWhatnot() // ... } }
我希望既能在Compose里调用这个方法,也能在和Compose无关的业务逻辑里调用它。
另外补充下:用户列表是存在Compose外部的,启动时从SQLite数据库加载,修改列表时要同步更新数据库。所以用copy的话没法自动更新数据库,还是得调用changeFoo方法更省事。我还好奇,复制对象的效率和直接修改单个值比怎么样?
我知道可能没法既用业务逻辑里的方法,又让Compose自动感知变化。如果是这样的话,我要么给Compose和非Compose场景写不同的方法,要么就用手动更新的方案。但如果有办法实现的话,我很想知道——毕竟我还是Compose新手,想多了解它的工作原理和大型项目中的最佳实践。
内容来源于stack exchange

