是否应该将Compose中的状态拆分
疑问
Compose中单个页面是否应该使用单个状态进行管理?
为什么会有以上这种疑问呢?难道不应该就是这样的吗?
其实不然,我们知道Compose是通过重组来刷新UI的,当一个State
发生改变时,其对应的可重组函数的Lambda会重新执行,为了方便,我们通常会会直接使用一个完整的State
来管理一个界面的显示。
所以我们在更新State
时,而这个State
的可重组范围是整个界面的Compose函数,基本会让整个界面都发生重组而不会跳过任何可重组项。
下面是一个简单的例子:
// 一个整合的State
data class XModelState(
val name: String,
val age: Int,
val intro: String
...
)
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
) {
println("Column")
Wrapper {
Text(text = "Name is ${modelState.name}").also {
println("Name changed")
}
}
Wrapper {
Text(text = "Age is ${modelState.age}").also {
println("Age changed")
}
}
Wrapper {
Text(text = "Intro is ${modelState.intro}").also {
println("Intro changed")
}
}
Button(
modifier = Modifier.fillMaxWidth(),
onClick = {
modelState = modelState.copy(
name = "Dougie ${Random.nextInt(0, 10)}",
age = age + 1
)
}
) {
Text(text = "Update Model")
}
}
以上代码中,当点击Update Model按钮来修改modelState时,3个Wrapper全部都发生重组。
flowchart LR A(Init State) -->|Click Button| B(State changed) --> |Trigger| C(re-compose)
但假设界面内容比较复杂,而这个State也很大很多属性,在更新其中某个或某些属性时,发生重组时,重组了整个可重组项,这不是我们想要的结果。我们想要的是,在某个或某些属性发生更新时,只重组使用到该属性的可重组项,而其他的则跳过(skip re-compose).
为了避免这个情况,目前Compose也没提供相关的api,好像也只能通过分割状态了,对应文章的标题。
拆分方式
1. ViewModel层保留多个公开的State
在ViewModel combine的时候就将State分割并提供多个State叫个UI层使用,而在操作的时候,我们也只会改变其对应的小State,其他State不会变化,
所以Compose上也只会重组其对应的可重组Lambda,而其他的则会跳过重组。
2. 使用 derivedStateOf
在View层,依然是拿到整个大的State,我们通过增加内部属性并使derivedStateOf这个方法将State分割。
val age by remember {
derivedStateOf {
modelState.age
}
}
val intro by remember {
derivedStateOf {
modelState.intro
}
}
如上,将age和intro分割出来,在点击按钮是,age+1,而intro不变,通过日志,发现只有Age Changed,所以intro的被跳过重组了。
Note: 在日常开发中,需求feature会越来越多,State也会随之而增加,以上两种方式虽然一定程度上能减少重组,但是也很大程度上增加了维护工作和带来新的问题。
总结
在Compose上,UI的更新必然脱离不了重组,所以重组本身并不是大问题,我们需要注意的是尽量在可重组项中尽量少操作多余的工作。所以通常我们不必在意这些,因为Compose帮我做了很多优化的工作了。
当然如果在某个UI上,有其中一部份UI变化相对比较频繁,而其他则很少,此时我建议对状态进行拆分