Jetpack Compose 中的状态管理:remember、mutableStateOf 以及其他
I’m happy to translate the article for you, but I’ll need the text of the post itself. Could you please paste the content you’d like translated (excluding any code blocks or URLs you want to keep unchanged)? Once I have the article text, I’ll provide a Simplified‑Chinese translation while preserving the original formatting and markdown.
理解组合与重组
在深入状态管理之前,了解 Compose 的工作原理非常重要。当你编写可组合函数时,它会作为 Compose 的 composition(组合)过程的一部分执行。随着状态的变化,Compose 会 recomposes(重新组合)——重新执行可组合函数以更新 UI。
Key challenge: 在可组合函数中本地创建的值会在每次重新组合时被重新创建。这正是状态管理工具发挥作用的地方。
Source: …
基础:remember 与 mutableStateOf
remember:在重组之间保留值
remember 是 Compose 中状态管理的基石。它可以让你在重组之间保留一个值:
@Composable
fun CounterExample() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Clicked $count times")
}
}
在这个例子中,count 会在重组之间保持。当按钮被点击时,状态会更新,触发一次重组以反映新的值。
mutableStateOf:创建可观察的状态
mutableStateOf 会创建一个 Compose 可观察的状态对象。当状态改变时,所有读取该状态的可组合函数都会被重新组合:
@Composable
fun LoginForm() {
val emailState = remember { mutableStateOf("") }
val passwordState = remember { mutableStateOf("") }
Column {
TextField(
value = emailState.value,
onValueChange = { emailState.value = it },
label = { Text("Email") }
)
TextField(
value = passwordState.value,
onValueChange = { passwordState.value = it },
label = { Text("Password") }
)
}
}
使用委托语法(var … by)更简洁、更符合惯用写法:
@Composable
fun LoginForm() {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Column {
TextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") }
)
TextField(
value = password,
onValueChange = { password = it },
label = { Text("Password") }
)
}
}
rememberSaveable: 通过配置更改保留状态
remember 能在重组期间保留状态,但它 不能 在配置更改(例如屏幕旋转)时存活。若需在此类更改中保持持久性,请使用 rememberSaveable:
@Composable
fun PersistentCounterExample() {
var count by rememberSaveable { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Clicked $count times (survives rotation)")
}
}
rememberSaveable 利用 Bundle 机制来保存和恢复状态,类似于传统的 Android 开发。
derivedStateOf: 从状态计算值
有时你需要根据状态变化计算一个值,但又不想让每一次变化都触发重新组合。derivedStateOf 会创建一个派生状态,仅在其值实际改变时才通知观察者:
@Composable
fun TextSearchExample() {
var searchQuery by remember { mutableStateOf("") }
// This expensive computation only runs when searchQuery actually changes
val searchResults by remember(searchQuery) {
derivedStateOf { performExpensiveSearch(searchQuery) }
}
Column {
TextField(
value = searchQuery,
onValueChange = { searchQuery = it },
label = { Text("Search") }
)
LazyColumn {
items(searchResults.size) { index ->
Text(searchResults[index])
}
}
}
}
即使 searchQuery 被快速更新,昂贵的搜索操作也只会在派生值实际改变时运行。
状态提升:提升状态
状态提升是一种设计模式,它将状态移动到公共的父可组合项中。这使得状态可以在多个子可组合项之间共享,并且更容易进行测试:
@Composable
fun ParentComponent() {
var sharedState by remember { mutableStateOf("") }
Column {
ChildComponentA(
state = sharedState,
onStateChange = { sharedState = it }
)
ChildComponentB(state = sharedState)
}
}
@Composable
fun ChildComponentA(
state: String,
onStateChange: (String) -> Unit
) {
TextField(
value = state,
onValueChange = onStateChange
)
}
@Composable
fun ChildComponentB(state: String) {
Text("Shared state: $state")
}
状态提升使你的可组合项更具可复用性且更易于测试,因为它们的行为仅依赖于参数,而不是内部状态。
ViewModel 与本地状态:何时使用各自
- 本地状态 (
remember,mutableStateOf) – 适用于仅在 UI 中使用且不需要在进程死亡后保留或在多个屏幕之间共享的状态。 - 基于
ViewModel的状态 – 当状态需要在配置更改后仍然保留、在 UI 不同部分的可组合函数之间共享,或需要在 UI 生命周期之外持久化时使用(例如,从仓库获取的数据)。
class CounterViewModel : ViewModel() {
private val _count = mutableStateOf(0)
val count: State = _count
fun increment() { _count.value++ }
}
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
val count by viewModel.count
Button(onClick = { viewModel.increment() }) {
Text("Clicked $count times")
}
}
在此示例中,计数因为位于 ViewModel 中而能够在配置更改后仍然保留。
小结
remember+mutableStateOf→ UI 本地、感知重组的状态。rememberSaveable→ 在配置更改后仍然保留的 UI 本地状态。derivedStateOf→ 高效的派生值,避免不必要的重组。- 状态提升 → 提升可重用性和可测试性。
ViewModel→ 处理共享的、长期存在的或跨进程存活的状态。
理解并运用这些工具将帮助您构建健壮、可维护且高性能的 Compose 应用。祝您编写 Compose 愉快!
使用 remember 管理不需要在进程死亡后保留的 UI 相关状态
典型用例
- 切换 UI 元素的可见性
- 编辑时的文本字段输入
- 滚动位置
临时 UI 状态
@Composable
fun ToggleVisibility() {
var isVisible by remember { mutableStateOf(true) }
Button(onClick = { isVisible = !isVisible }) {
Text(if (isVisible) "Hide" else "Show")
}
if (isVisible) {
Text("Content is visible")
}
}
ViewModel 用于在进程死亡后仍然存活的业务逻辑和数据
典型用例
- 来自数据库或 API 的用户数据
- 整个应用的状态
- 业务逻辑
在应用生命周期中持久化的状态
class UserViewModel : ViewModel() {
private val _userState = MutableStateFlow<User?>(null)
val userState: StateFlow<User?> = _userState.asStateFlow()
init {
loadUser()
}
private fun loadUser() {
viewModelScope.launch {
_userState.value = userRepository.getUser()
}
}
}
@Composable
fun UserScreen(viewModel: UserViewModel = hiltViewModel()) {
val user by viewModel.userState.collectAsState()
user?.let {
Text("Welcome, ${it.name}")
}
}
将 ViewModel 与本地状态结合
最稳健的模式是将两种方法混合使用:
class ProductViewModel : ViewModel() {
private val _products = MutableStateFlow<List<Product>>(emptyList())
val products: StateFlow<List<Product>> = _products.asStateFlow()
init {
loadProducts()
}
private fun loadProducts() {
viewModelScope.launch {
_products.value = productRepository.getProducts()
}
}
}
@Composable
fun ProductListScreen(viewModel: ProductViewModel = hiltViewModel()) {
val products by viewModel.products.collectAsState()
var selectedProductId by remember { mutableStateOf<String?>(null) }
Row {
LazyColumn(modifier = Modifier.weight(1f)) {
items(products) { product ->
ProductItem(
product = product,
isSelected = selectedProductId == product.id,
onSelect = { selectedProductId = product.id }
)
}
}
selectedProductId?.let { id ->
ProductDetails(
product = products.find { it.id == id },
modifier = Modifier.weight(1f)
)
}
}
}
最佳状态管理实践
- 尽可能向上提升状态 – 将状态移动到需要它的可组合函数的最近公共父组件。
- 让状态靠近使用位置 – 不要提升得比必要的更高,这样可以保持可复用性。
- 使用
ViewModel保存持久状态 – 必须在进程死亡后仍然存在的数据应放在ViewModel中。 - 避免可变的共享状态 – 更倾向使用不可变数据结构和单向数据流。
- 使用提升的状态来测试可组合函数 – 被提升的状态使得可组合函数更易于测试,因为可以注入测试值。
- 使用
rememberSaveable保存 UI 状态 – 当 UI 状态需要在配置更改后仍然保留时,使用rememberSaveable包裹它。
结论
一旦了解了核心工具,Jetpack Compose 中的状态管理就变得直观:
remember/mutableStateOf→ 本地的、临时的 UI 状态rememberSaveable→ 能够在配置更改后仍然保留的 UI 状态ViewModel→ 持久的业务逻辑和数据
为每种情况选择合适的工具:对瞬时 UI 需求使用本地状态,对需要在 UI 之外存活的数据使用 ViewModel,并提升状态以在可组合函数之间高效共享。
所有 8 个模板都展示了正确的状态管理。
https://myougatheax.gumroad.com