/ Android  Jetpack Compose  状态管理  ViewModel  StateFlow  Kotlin  UI开发  移动开发 

Jetpack Compose 状态管理全解析:从 remember 到 ViewModel 最佳实践


封面

什么是 Jetpack Compose 状态管理

在 Jetpack Compose 中,UI 是状态的函数:UI = f(State)。状态(State)的任何变化都会触发对应 Composable 函数的重组(Recomposition),从而自动更新界面。理解这一核心理念,是掌握 Compose 开发的第一步。

与传统 View 体系手动调用 setText()setVisibility() 等方式不同,Compose 采用声明式编程范式——你只需描述在某种状态下 UI 应该长什么样,框架负责高效地将当前 UI 更新到目标状态。

  • 单向数据流(UDF):事件向上传递,状态向下流动

  • 不可变状态:每次状态变化产生新的状态对象

  • 自动重组:只有读取了该状态的 Composable 才会重新执行

remember 与 mutableStateOf 基础用法

remember 用于在重组之间保留状态,mutableStateOf 创建一个可观察的可变状态持有者。两者结合是 Compose 本地状态管理的基础写法:

@Composable
fun CounterButton() {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("点击次数:$count")
    }
}

注意 by 关键字通过属性委托简化了 .value 的读写。等价写法如下:

val count = remember { mutableStateOf(0) }
// 读取:count.value
// 写入:count.value = newValue

常见的状态类型还有 mutableStateListOfmutableStateMapOf,分别用于管理列表和映射类型的响应式状态。

状态提升(State Hoisting)最佳实践

当同一状态需要被多个 Composable 共享或由父级控制时,应将状态"提升"到共同的父 Composable 中,子组件只接收状态值和回调函数。这就是 Compose 推崇的状态提升(State Hoisting)模式:

// 无状态子组件(Stateless)
@Composable
fun SearchBar(
    query: String,
    onQueryChange: (String) -> Unit,
    modifier: Modifier = Modifier
) {
    OutlinedTextField(
        value = query,
        onValueChange = onQueryChange,
        label = { Text("搜索") },
        modifier = modifier
    )
}

// 有状态父组件(Stateful)
@Composable
fun SearchScreen() {
    var query by remember { mutableStateOf("") }
    SearchBar(
        query = query,
        onQueryChange = { query = it }
    )
    // 展示搜索结果
    SearchResults(query = query)
}

状态提升的好处在于:子组件更易测试、可复用性更强,同时状态的来源更加清晰可追溯。

ViewModel + StateFlow 集成:跨重组周期的状态管理

对于需要跨越 Activity/Fragment 生命周期的业务状态,应当将状态托管在 ViewModel 中,并通过 StateFlow 向 UI 层暴露状态:

// ViewModel 层
class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UserUiState())
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()

    fun loadUser(userId: String) {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true) }
            val user = userRepository.getUser(userId)
            _uiState.update { it.copy(isLoading = false, user = user) }
        }
    }
}

data class UserUiState(
    val isLoading: Boolean = false,
    val user: User? = null,
    val errorMessage: String? = null
)

在 Composable 中使用 collectAsStateWithLifecycle() 收集 Flow(需要引入 androidx.lifecycle:lifecycle-runtime-compose):

@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()

    when {
        uiState.isLoading -> CircularProgressIndicator()
        uiState.user != null -> UserContent(user = uiState.user!!)
        uiState.errorMessage != null -> ErrorMessage(uiState.errorMessage!!)
    }
}

collectAsStateWithLifecycle() 相比 collectAsState() 的优势在于:它会在生命周期进入 STOPPED 状态时自动停止收集,节省资源,是 Google 官方推荐的最佳实践。

性能优化:避免不必要的重组

重组是 Compose 的核心机制,但过度重组会影响性能。以下是几个常见的优化技巧:

  • 使用 derivedStateOf:当状态 B 依赖状态 A 计算得出时,用 derivedStateOf 避免不必要的重组:

val listState = rememberLazyListState()
val showScrollToTop by remember {
    derivedStateOf { listState.firstVisibleItemIndex > 0 }
}
  • 稳定性标注:对 ViewModel 传递的数据类添加 @Stable@Immutable 注解,帮助编译器跳过不必要的重组检查。

  • key() 函数:在列表中为每个 item 提供稳定的 key,避免列表变化时重组所有项目。

  • Lambda 引用:将 onClick 等 Lambda 提取为具名函数或用 remember { {} } 包裹,防止每次重组都创建新对象。

// ❌ 每次重组都会创建新 Lambda
Button(onClick = { viewModel.onItemClick(id) })

// ✅ 用 remember 缓存,减少不必要的重组
val onClick = remember(id) { { viewModel.onItemClick(id) } }
Button(onClick = onClick)

合理运用上述技巧,可以让你的 Compose 应用达到丝滑的 60fps/120fps 渲染性能,为用户带来流畅的交互体验。

发布评论

热门评论区: