jetpack compose 界面刷新的几种方式 如何避免无效的界面刷新

news2025/6/2 19:56:11

界面刷新的几种方式

在 Jetpack Compose 中,界面刷新主要依赖于数据的响应式变化。以下是几种常见的界面刷新方式及其原理:

1. 使用 MutableState(基础方式)

通过 mutableStateOf 创建可观察的状态,状态变化时会触发重组(Recomposition)。

@Composable
fun Counter() {
    // 创建可变状态
    var count by remember { mutableStateOf(0) }
    
    Button(onClick = { count++ }) {
        Text("点击了 $count 次") // 数据变化自动触发界面刷新
    }
}

原理

  • mutableStateOf 返回一个 State<T> 对象,其值变化时会标记使用该状态的 Composable 需重组。
  • by remember 语法糖自动委托给 getValue/setValue 方法,简化状态管理。

2. 使用 ViewModelStateFlow/LiveData

将状态提升到 ViewModel,通过数据流驱动界面更新。

class MainViewModel : ViewModel() {
    private val _count = MutableStateFlow(0)
    val count: StateFlow<Int> = _count
    
    fun increment() {
        _count.value++
    }
}

@Composable
fun CounterScreen(viewModel: MainViewModel = viewModel()) {
    // 收集 StateFlow 并转换为 Compose 状态
    val count by viewModel.count.collectAsState()
    
    Button(onClick = { viewModel.increment() }) {
        Text("点击了 $count 次")
    }
}

原理

  • collectAsState() 将 Flow 转换为可观察的 Compose 状态,Flow 发射新值时触发重组。
  • 自动处理生命周期感知,避免内存泄漏。

3. 使用 derivedStateOf 计算派生状态

当状态依赖于其他状态时,使用 derivedStateOf 缓存计算结果,减少不必要的重组。

@Composable
fun DerivedStateExample() {
    var text by remember { mutableStateOf("") }
    
    // 派生状态:计算文本长度
    val length by derivedStateOf {
        text.length
    }
    
    TextField(
        value = text,
        onValueChange = { text = it },
        label = { Text("输入文本") }
    )
    Text("文本长度:$length")
}

原理

  • derivedStateOf 会缓存计算结果,只有依赖的状态变化时才重新计算。
  • 适用于复杂计算或需要优化性能的场景。

4. 使用 produceState 处理异步操作

将异步数据流(如网络请求、数据库查询)转换为 Compose 状态。

@Composable
fun FetchDataExample() {
    val result by produceState<Result<String>?>(initialValue = null) {
        // 在后台协程中执行异步操作
        value = try {
            Result.success(repository.fetchData())
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    when (result) {
        is Result.Success -> Text("数据: ${result.data}")
        is Result.Failure -> Text("错误: ${result.exception.message}")
        null -> CircularProgressIndicator()
    }
}

原理

  • produceState 在协程中执行异步操作,并将结果更新到状态中。
  • 自动处理加载状态和错误状态,避免竞态条件。

5. 使用 LaunchedEffect 触发副作用更新

当需要在重组后执行副作用(如网络请求、动画),并更新状态时使用。

@Composable
fun SearchScreen(query: String) {
    var results by remember { mutableStateOf(emptyList<String>()) }
    
    LaunchedEffect(query) { // 当 query 变化时重新执行
        results = searchRepository.search(query)
    }
    
    LazyColumn {
        items(results) { item -> Text(item) }
    }
}

原理

  • LaunchedEffect 在组合后启动协程,避免在重组时重复执行。
  • 可通过 key 参数控制何时重新启动协程。

6. 使用 Animatable 实现动画驱动的更新

通过动画值的变化触发界面刷新,实现平滑过渡。

@Composable
fun AnimatedCounter() {
    val animatable = remember { Animatable(0f) }
    
    Button(onClick = {
        // 启动动画,从当前值过渡到 100f
        animatable.animateTo(100f)
    }) {
        Text("当前值: ${animatable.value.roundToInt()}")
    }
}

原理

  • Animatable 是一个可动画的状态,值变化时会触发重组。
  • 可自定义动画参数(如持续时间、缓动函数)。

7. 手动触发刷新(不推荐)

极少数情况下,可通过 rememberUpdatedStateSideEffect 手动控制刷新。

@Composable
fun ManualRefreshExample() {
    var refreshKey by remember { mutableStateOf(0) }
    
    Button(onClick = { refreshKey++ }) {
        Text("刷新")
    }
    
    // 当 refreshKey 变化时,该 Composable 会重组
    key(refreshKey) {
        ExpensiveContent()
    }
}

原理

  • key 组件会在 key 值变化时强制重组其内容。
  • 仅用于性能优化或特殊场景,避免滥用。

总结

方式适用场景示例
mutableStateOf简单本地状态管理计数器、文本输入框状态
StateFlow/LiveData跨组件状态共享、异步数据流网络请求结果展示、数据库数据监听
derivedStateOf计算派生状态,优化性能文本长度、列表过滤结果
produceState处理异步操作加载网络数据、读取文件
LaunchedEffect触发副作用并更新状态搜索联想、定时任务
Animatable实现平滑动画数值变化动画、过渡效果
key 组件手动控制重组强制刷新复杂组件

核心原则:在 Compose 中,界面是数据的函数,尽量通过状态变化驱动界面更新,避免命令式操作。

如何避免无效的界面刷新

在 Jetpack Compose 中,避免无效的界面刷新(即不必要的重组)是性能优化的关键。Compose 的重组机制虽然高效,但仍需遵循一些最佳实践来减少不必要的计算。以下是几种主要方法:

1. 使用 @Stable 注解标记稳定类型

Compose 通过检查对象的稳定性来判断是否需要重组。为自定义数据类添加 @Stable 注解或确保其满足稳定条件:

// 稳定类:所有属性都是稳定类型,且没有可变状态
@Stable
data class User(
    val name: String,     // String 是稳定类型
    val age: Int          // Int 是稳定类型
)

// 使用稳定类型的 Composable
@Composable
fun UserCard(user: User) {
    Text("姓名: ${user.name}")
    Text("年龄: ${user.age}")
}

原理

  • 稳定类型的属性变化时,Compose 能精确识别并只重组受影响的部分。
  • 避免使用非稳定类型(如自定义类未实现 equals()/hashCode())作为状态。

2. 使用 key() 组件优化列表重组

在处理动态列表时,使用 key() 为每个项提供唯一标识,避免整体重组:

@Composable
fun UserList(users: List<User>) {
    LazyColumn {
        items(
            items = users,
            key = { user -> user.id } // 使用唯一 ID 作为 key
        ) { user ->
            UserCard(user)
        }
    }
}

原理

  • 当列表顺序或内容变化时,key 帮助 Compose 识别哪些项被添加、删除或移动,只重组变化的部分。
  • 避免使用索引作为 key(除非列表内容固定不变),否则可能导致意外的重组。

3. 使用 derivedStateOf 缓存计算结果

当状态依赖于其他状态时,使用 derivedStateOf 避免重复计算:

@Composable
fun SearchResults(items: List<String>, query: String) {
    // 仅当 items 或 query 变化时才重新计算
    val filteredItems by derivedStateOf {
        items.filter { it.contains(query, ignoreCase = true) }
    }
    
    LazyColumn {
        items(filteredItems) { item ->
            Text(item)
        }
    }
}

原理

  • derivedStateOf 会缓存计算结果,只有依赖的状态变化时才重新计算。
  • 适用于复杂计算(如列表过滤、字符串处理)。

4. 使用 remember 缓存不可变对象

通过 remember 缓存创建成本高的对象,避免每次重组时重新创建:

@Composable
fun ImageLoaderExample(url: String) {
    // 缓存 ImageLoader 实例,避免重复创建
    val imageLoader = remember { MyImageLoader(context) }
    
    Image(
        painter = imageLoader.load(url),
        contentDescription = null
    )
}

原理

  • remember 会在重组时保留对象引用,仅当 key 变化时重新计算。
  • 可通过传入 key 参数(如 remember(url) { ... })控制何时重新创建。

5. 提取子组件为独立 Composable

将不依赖外部状态的 UI 部分提取为单独的 Composable,减少重组范围:

@Composable
fun ParentComponent() {
    var count by remember { mutableStateOf(0) }
    
    // 独立子组件:不依赖 count,变化时不会触发此组件重组
    StaticContent()
    
    Button(onClick = { count++ }) {
        Text("计数: $count")
    }
}

@Composable
fun StaticContent() {
    Text("这是固定内容,不会随计数变化而重组")
}

原理

  • Compose 的重组是局部的,子组件不依赖的状态变化不会触发其重组。
  • 避免在大型 Composable 中混合静态和动态内容。

6. 使用 @Composable 函数参数替代 Lambda

将复杂逻辑封装在 @Composable 函数中,而非直接传递 Lambda:

// 避免:每次重组时重新创建 Lambda
@Composable
fun BadExample(users: List<User>) {
    LazyColumn {
        items(users) { user ->
            UserCard(
                onClick = { /* 复杂逻辑 */ } // 每次重组时重新创建
            )
        }
    }
}

// 推荐:使用 @Composable 函数参数
@Composable
fun GoodExample(users: List<User>) {
    LazyColumn {
        items(users) { user ->
            UserCard(
                onClick = remember(user) { { /* 使用 remember 缓存 */ } }
            )
        }
    }
}

原理

  • Lambda 表达式默认是非稳定的,会导致不必要的重组。
  • 使用 remember 缓存 Lambda 或提取为单独的 @Composable 函数。

7. 使用 MutableStateFlow 替代 mutableStateOf 处理复杂状态

对于跨组件共享的复杂状态,使用 MutableStateFlow 结合 collectAsState(),避免状态提升导致的过度重组:

class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState = _uiState.asStateFlow()
    
    fun updateState() {
        _uiState.value = _uiState.value.copy(/* 更新部分状态 */)
    }
}

@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
    val state by viewModel.uiState.collectAsState()
    
    // 仅当 state.importantData 变化时重组
    ImportantContent(data = state.importantData)
    
    // 仅当 state.otherData 变化时重组
    OtherContent(data = state.otherData)
}

原理

  • StateFlow 允许细粒度控制状态变化,不同组件可观察不同部分的状态。

8. 使用 CompositionLocalProvider 避免深层传递状态

对于多层嵌套的组件,使用 CompositionLocal 避免状态通过参数层层传递:

// 定义 CompositionLocal
val LocalUser = compositionLocalOf<User> { error("No user provided") }

@Composable
fun App() {
    val user = remember { User("John", 30) }
    
    CompositionLocalProvider(LocalUser provides user) {
        DeeplyNestedComponent()
    }
}

@Composable
fun DeeplyNestedComponent() {
    // 直接获取 LocalUser,无需逐层传递
    val user = LocalUser.current
    Text("姓名: ${user.name}")
}

原理

  • CompositionLocal 允许在组件树中任意位置获取值,减少中间组件的重组。

9. 避免在 Composable 中执行耗时操作

将耗时操作(如网络请求、文件读取)放在 ViewModel 或协程中,避免阻塞重组:

@Composable
fun DataScreen() {
    val viewModel: MyViewModel = viewModel()
    val data by viewModel.dataFlow.collectAsState()
    
    when (data) {
        is Result.Loading -> CircularProgressIndicator()
        is Result.Success -> Text("数据: ${data.value}")
        is Result.Error -> Text("错误: ${data.message}")
    }
}

// ViewModel 中处理异步操作
class MyViewModel : ViewModel() {
    private val _dataFlow = MutableStateFlow<Result<String>>(Result.Loading)
    val dataFlow: StateFlow<Result<String>> = _dataFlow
    
    init {
        viewModelScope.launch {
            try {
                val result = fetchDataFromNetwork() // 耗时操作
                _dataFlow.value = Result.Success(result)
            } catch (e: Exception) {
                _dataFlow.value = Result.Error(e.message ?: "未知错误")
            }
        }
    }
}

原理

  • 重组应该是快速的纯函数调用,耗时操作会导致界面卡顿并触发更多重组。

10. 使用 @Preview 和 Profiler 检测性能问题

通过 Android Studio 的 Profiler 工具分析重组情况,找出过度刷新的组件:

@Preview
@Composable
fun PreviewMyComponent() {
    MyComponent(data = PreviewData)
}

步骤

  1. 在 Profiler 中选择 “Compose” 标签。
  2. 观察重组次数和耗时,检查是否存在意外的重组。
  3. 使用 @Stablekey() 优化问题组件。

总结

避免无效刷新的核心原则是:

  • 最小化状态作用域:将状态限制在使用它的最小组件范围内。
  • 最大化稳定性:使用 @Stableremember 和不可变数据结构。
  • 优化重组范围:通过提取组件、使用 key()derivedStateOf 减少重组区域。

通过这些方法,你可以充分发挥 Compose 的性能优势,打造流畅高效的应用。

独立 Composable 如何进行界面刷新

在 Jetpack Compose 中,独立 Composable(即不依赖外部状态的组件)的界面刷新主要通过内部状态管理或外部事件触发。以下是几种常见的实现方式:

1. 使用内部状态(mutableStateOf

独立组件可以通过自身的 mutableStateOf 管理状态变化,从而触发刷新。

@Composable
fun ToggleButton() {
    // 内部状态:记录按钮是否被点击
    var isChecked by remember { mutableStateOf(false) }
    
    Button(
        onClick = { isChecked = !isChecked },
        colors = ButtonDefaults.buttonColors(
            backgroundColor = if (isChecked) Color.Green else Color.Gray
        )
    ) {
        Text(text = if (isChecked) "已开启" else "已关闭")
    }
}

原理

  • mutableStateOf 创建一个可观察的状态对象,当状态值变化时,使用该状态的 Composable 会自动重组。
  • remember 确保状态在重组时保持不变,避免重复初始化。

2. 通过事件回调触发外部状态更新

独立组件可以通过回调函数将事件传递给父组件,由父组件更新状态并重新渲染。

// 独立组件:接收回调函数
@Composable
fun ChildComponent(onValueChange: (String) -> Unit) {
    var text by remember { mutableStateOf("") }
    
    TextField(
        value = text,
        onValueChange = {
            text = it
            onValueChange(it) // 将变化传递给父组件
        },
        label = { Text("输入内容") }
    )
}

// 父组件:管理状态
@Composable
fun ParentComponent() {
    var inputText by remember { mutableStateOf("") }
    
    Column {
        ChildComponent(onValueChange = { inputText = it })
        Text("你输入的内容:$inputText")
    }
}

原理

  • 单向数据流模式:子组件不直接管理状态,而是通过回调通知父组件,由父组件更新状态并触发重组。

3. 使用 produceState 处理异步刷新

对于需要异步数据的独立组件,可以使用 produceState 将异步操作转换为可观察的状态。

@Composable
fun WeatherWidget(city: String) {
    // 将异步网络请求转换为状态
    val weather by produceState<WeatherData?>(initialValue = null) {
        // 在后台协程中执行请求
        value = fetchWeatherData(city)
    }
    
    when (weather) {
        null -> Text("加载中...")
        is WeatherData.Success -> Text("${weather.city}: ${weather.temp}°C")
        is WeatherData.Error -> Text("错误: ${weather.message}")
    }
}

// 模拟异步网络请求
private suspend fun fetchWeatherData(city: String): WeatherData {
    delay(1000) // 模拟网络延迟
    return WeatherData.Success(city, 25.5)
}

sealed class WeatherData {
    data class Success(val city: String, val temp: Double) : WeatherData()
    data class Error(val message: String) : WeatherData()
}

原理

  • produceState 在协程中执行异步操作,并将结果更新到状态中。状态变化时触发组件重组。

4. 通过 Animatable 实现动画驱动的刷新

独立组件可以使用 Animatable 创建动画,通过动画值的变化触发连续刷新。

@Composable
fun AnimatedButton() {
    // 创建可动画的状态
    val scale by remember { Animatable(1f) }.run {
        // 点击时启动动画
        LaunchedEffect(Unit) {
            while (isActive) {
                animateTo(1.2f, animationSpec = tween(500))
                animateTo(1f, animationSpec = tween(500))
            }
        }
        asState() // 将 Animatable 转换为 State 对象
    }
    
    Button(
        onClick = { /* 点击事件 */ },
        modifier = Modifier.scale(scale)
    ) {
        Text("脉动按钮")
    }
}

原理

  • Animatable 是一个可动画的数值,其值在动画过程中不断变化,每次变化都会触发组件重组,从而实现平滑动画效果。

5. 使用 FlowcollectAsState 监听外部变化

独立组件可以通过 collectAsState 收集外部 Flow 的变化,实现被动刷新。

// 假设这是一个全局状态或 ViewModel 中的 Flow
val timeFlow = flow {
    while (true) {
        emit(LocalTime.now())
        delay(1000) // 每秒更新一次
    }
}.flowOn(Dispatchers.Default)

@Composable
fun ClockWidget() {
    // 收集 Flow 并转换为状态
    val currentTime by timeFlow.collectAsState(initial = LocalTime.now())
    
    Text(
        text = currentTime.format(DateTimeFormatter.ofPattern("HH:mm:ss")),
        fontSize = 24.sp
    )
}

原理

  • collectAsState 将 Flow 转换为 Compose 状态,Flow 发射新值时触发组件重组。

6. 使用 derivedStateOf 计算派生状态

当组件状态依赖于其他状态时,使用 derivedStateOf 缓存计算结果,避免重复计算。

@Composable
fun FormValidation() {
    var username by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }
    
    // 派生状态:根据用户名和密码计算验证结果
    val isValid by derivedStateOf {
        username.length >= 3 && password.length >= 6
    }
    
    Column {
        TextField(
            value = username,
            onValueChange = { username = it },
            label = { Text("用户名") }
        )
        
        TextField(
            value = password,
            onValueChange = { password = it },
            label = { Text("密码") }
        )
        
        Button(
            onClick = { /* 登录逻辑 */ },
            enabled = isValid
        ) {
            Text("登录")
        }
    }
}

原理

  • derivedStateOf 会缓存计算结果,只有当依赖的状态(usernamepassword)变化时才重新计算。

总结

独立 Composable 的刷新机制核心在于:

  1. 内部状态管理:使用 mutableStateOfAnimatable 等管理自身状态变化。
  2. 外部事件驱动:通过回调函数将事件传递给父组件,由父组件更新状态。
  3. 异步数据流:使用 produceStatecollectAsState 处理异步数据变化。
  4. 状态派生与缓存:使用 derivedStateOf 优化计算密集型操作。

通过这些方法,独立组件可以在不依赖外部状态管理库的情况下,实现高效的界面刷新。

在有状态的独立Composable中,如何处理多个状态的变化?

在有状态的独立 Composable 中处理多个状态变化时,需要考虑状态的关联性、更新的原子性以及代码的可维护性。以下是几种常见的处理方式:

1. 分离独立状态(简单场景)

对于相互独立的状态,可以使用多个 mutableStateOf 分别管理。

@Composable
fun MultiStateComponent() {
    // 独立状态:用户名和密码
    var username by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }
    
    Column {
        TextField(
            value = username,
            onValueChange = { username = it },
            label = { Text("用户名") }
        )
        
        TextField(
            value = password,
            onValueChange = { password = it },
            label = { Text("密码") }
        )
        
        Button(
            onClick = { /* 登录逻辑 */ }
        ) {
            Text("登录")
        }
    }
}

适用场景

  • 状态之间没有逻辑关联。
  • 状态更新相互独立,不会同时触发。

2. 合并关联状态为数据类(推荐方式)

将相关状态封装到数据类中,使用单一 mutableStateOf 管理,确保原子性更新。

@Composable
fun FormComponent() {
    // 合并状态为数据类
    var formState by remember {
        mutableStateOf(FormState())
    }
    
    Column {
        TextField(
            value = formState.username,
            onValueChange = { 
                // 局部更新:创建新的状态对象
                formState = formState.copy(username = it)
            },
            label = { Text("用户名") }
        )
        
        TextField(
            value = formState.password,
            onValueChange = { 
                formState = formState.copy(password = it)
            },
            label = { Text("密码") }
        )
        
        Checkbox(
            checked = formState.rememberMe,
            onCheckedChange = { 
                formState = formState.copy(rememberMe = it)
            }
        )
        
        Button(
            onClick = { /* 使用 formState 处理登录 */ }
        ) {
            Text("登录")
        }
    }
}

// 数据类封装表单状态
data class FormState(
    val username: String = "",
    val password: String = "",
    val rememberMe: Boolean = false
)

优点

  • 状态更新是原子性的,避免中间状态导致的 UI 闪烁。
  • 便于管理状态的生命周期和依赖关系。
  • 简化状态重置逻辑(只需创建新的初始对象)。

3. 使用 LaunchedEffect 处理状态间副作用

当一个状态变化需要触发另一个状态的更新时,使用 LaunchedEffect 处理副作用。

@Composable
fun SearchComponent() {
    var query by remember { mutableStateOf("") }
    var results by remember { mutableStateOf(emptyList<String>()) }
    
    // 当查询词变化时,触发搜索
    LaunchedEffect(query) {
        if (query.isNotEmpty()) {
            // 模拟搜索延迟
            delay(300)
            results = searchDatabase(query)
        }
    }
    
    Column {
        TextField(
            value = query,
            onValueChange = { query = it },
            label = { Text("搜索") }
        )
        
        LazyColumn {
            items(results) { result ->
                Text(result)
            }
        }
    }
}

// 模拟搜索数据库
private suspend fun searchDatabase(query: String): List<String> {
    // 实际项目中可能是网络请求或数据库查询
    return listOf("结果1", "结果2", "结果3")
}

原理

  • LaunchedEffectquery 变化时启动新协程,避免阻塞主线程。
  • 协程完成后更新 results 状态,触发 UI 重组。

4. 使用 derivedStateOf 计算派生状态

当某些状态是其他状态的计算结果时,使用 derivedStateOf 避免重复计算。

@Composable
fun ShoppingCart() {
    var items by remember { mutableStateOf(listOf("苹果", "香蕉", "橙子")) }
    var discount by remember { mutableStateOf(0.8f) }
    
    // 计算总价格(派生状态)
    val totalPrice by derivedStateOf {
        items.size * 10.0 * discount // 每件商品10元,打折后价格
    }
    
    Column {
        LazyColumn {
            items(items) { item ->
                Text(item)
            }
        }
        
        Text("原价: ${items.size * 10.0} 元")
        Text("折扣: ${(1 - discount) * 100}%")
        Text("总价: $totalPrice 元")
        
        Button(onClick = { discount = 0.7f }) {
            Text("使用7折优惠")
        }
    }
}

优点

  • 仅当依赖状态(itemsdiscount)变化时重新计算。
  • 缓存计算结果,提高性能。

5. 使用 mutableStateListOf 管理动态列表

对于需要频繁增删改的列表状态,使用 mutableStateListOf 可以更精确地控制重组范围。

@Composable
fun TodoList() {
    // 使用 mutableStateListOf 创建可观察列表
    val todos = remember { mutableStateListOf<String>() }
    
    Column {
        TextField(
            value = "",
            onValueChange = { /* 临时输入状态 */ },
            label = { Text("添加待办事项") }
        )
        
        Button(onClick = { todos.add("新任务") }) {
            Text("添加")
        }
        
        LazyColumn {
            items(todos) { todo ->
                Row {
                    Text(todo)
                    Button(onClick = { todos.remove(todo) }) {
                        Text("删除")
                    }
                }
            }
        }
    }
}

优点

  • 列表项变化时,仅重组受影响的行,而非整个列表。
  • 提供直接操作列表的方法(如 addremoveupdate)。

6. 使用 produceState 处理异步多状态

当多个状态依赖于异步操作时,使用 produceState 统一管理状态变化。

@Composable
fun UserProfile(userId: String) {
    // 使用 produceState 处理异步加载
    val userState by produceState<UserState>(initialValue = UserState.Loading) {
        try {
            // 模拟并发加载用户信息和头像
            val (userInfo, avatar) = coroutineScope {
                val userDeferred = async { fetchUserInfo(userId) }
                val avatarDeferred = async { fetchAvatar(userId) }
                userDeferred.await() to avatarDeferred.await()
            }
            
            // 更新为成功状态
            value = UserState.Success(userInfo, avatar)
        } catch (e: Exception) {
            // 更新为错误状态
            value = UserState.Error(e.message ?: "加载失败")
        }
    }
    
    when (userState) {
        is UserState.Loading -> CircularProgressIndicator()
        is UserState.Success -> {
            Text("用户名: ${userState.user.name}")
            Image(
                painter = painterResource(userState.avatar),
                contentDescription = "用户头像"
            )
        }
        is UserState.Error -> Text("错误: ${userState.message}")
    }
}

// 状态密封类
sealed class UserState {
    object Loading : UserState()
    data class Success(val user: UserInfo, val avatar: Int) : UserState()
    data class Error(val message: String) : UserState()
}

原理

  • produceState 在后台协程中执行异步操作,确保 UI 响应性。
  • 统一管理加载中、成功和错误状态,避免竞态条件。

总结

处理多状态变化的核心策略:

  1. 分离 vs 合并:独立状态分离管理,关联状态合并为数据类。
  2. 原子性更新:使用 copy() 方法确保状态更新的原子性。
  3. 副作用处理:通过 LaunchedEffect 处理状态间的副作用。
  4. 计算优化:使用 derivedStateOf 缓存派生状态的计算结果。
  5. 动态列表:使用 mutableStateListOf 高效管理列表变化。
  6. 异步统一管理:使用 produceState 处理复杂异步状态流。

通过合理选择状态管理方式,可以保持代码的简洁性和可维护性,同时优化界面刷新性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2394101.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【STM32+LAN9252+HAL库】EtherCAT从站搭建 保姆级教程

目录 一、生成协议栈及XML文件 二、使用stm32CuboMX配置外设 三、协议栈移植 鉴于本人对EtherCAT的掌握程度十分有限&#xff0c;这篇文章仅作为我搭建基础从站的过程记录不做更多讲解。本文内容主要为SPI模式的基础搭建&#xff0c;更多深入的学习资料和细节&#xff0c;大家…

【harbor】--基础使用

推送 不同的管理工具都有说明 以docker为例 # 第一步--打标签 docker tag SOURCE_IMAGE[:TAG] 192.168.121.201:801/haohao_fist/REPOSITORY[:TAG] # 第二步--推送 docker push 192.168.121.201:801/haohao_fist/REPOSITORY[:TAG]默认push推送为https push会失败 解决办法…

JAVA学习 DAY1 初识JAVA

本系列可作为JAVA学习系列的笔记&#xff0c;文中提到的一些练习的代码&#xff0c;小编会将代码复制下来&#xff0c;大家复制下来就可以练习了&#xff0c;方便大家学习。 点赞关注不迷路&#xff01;您的点赞、关注和收藏是对小编最大的支持和鼓励&#xff01; 系列文章目录…

Vue能启动但访问空白?并报”export ‘default’ (imported as ‘Vue’) was not found in ‘vue’

场景 如图&#xff0c;vue项目的node_modules下载顺利&#xff0c;启动也顺利&#xff0c;但是访问却为空白页面 虽然页面是空白&#xff0c;但是通过浏览器控制台可以看出并非简单的空白&#xff0c;确实有不兼容问题在里面 分析问题 从上图浏览器控制台可以看出&#xff0c…

Electron-vite【实战】MD 编辑器 -- 系统菜单(含菜单封装,新建文件,打开文件,打开文件夹,保存文件,退出系统)

最终效果 整体架构 src/main/index.ts import { createMenu } from ./menu在 const mainWindow 后 // 加载菜单createMenu(mainWindow)src/main/menu.ts import { BrowserWindow, Menu, MenuItem, MenuItemConstructorOptions, dialog, shell } from electron import fs from…

【Docker系列】Docker 容器内安装`ps`命令

博客目录 一、为什么需要在 Docker 容器中安装ps命令二、不同 Linux 发行版的安装方法1. Alpine Linux 镜像的安装方法2. Debian/Ubuntu 镜像的安装方法3. CentOS/RHEL 镜像的安装方法 三、验证安装与基本使用四、永久解决方案&#xff1a;修改 Dockerfile1. Alpine 基础镜像的…

华为OD机试真题——生成哈夫曼树(2025A卷:100分)Java/python/JavaScript/C/C++/GO六种最佳实现

2025 A卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 本文收录于专栏:《2025华为OD真题目录+全流程解析/备考攻略/经验分享》 华为OD机试真题《生成…

大厂前端研发岗位设计的30道Webpack面试题及解析

文章目录 一、基础核心二、配置进阶三、性能优化四、Loader原理五、Plugin机制六、高级应用七、工程化实战八、原理深挖九、异常处理十、综合场景一、基础核心 Webpack的核心概念是什么? 解析:入口(entry)、输出(output)、加载器(loader)、插件(plugins)、模式(mode)。Loader…

Oracle中EXISTS NOT EXISTS的使用

目录 1.IN与EXISTS EXISTS用法总结 2.NOT IN与NOT EXISTS 3.not in 中 null的用法 4.EXISTS和IN的区别 (面试常问) 1.IN与EXISTS 示例&#xff1a;在 DEPT 表中找出在 EMP 表中存在的部门编号&#xff1b; 方法一&#xff1a;使用in select DEPTNO from DEPT where D…

01.认识Kubernetes

什么是Kubernets 套用官方文档对Kubernetes的定义&#xff0c;翻译成中文的意思是&#xff1a; Kubernetes&#xff0c;也称为k8&#xff0c;是一个用于自动化部署、扩展和管理容器化应用程序的开源系统。 它将组成应用程序的容器分组为逻辑单元&#xff0c;以便于管理和发现…

【PostgreSQL 02】PostgreSQL数据类型革命:JSON、数组与地理信息让你的应用飞起来

PostgreSQL数据类型革命&#xff1a;JSON、数组与地理信息让你的应用飞起来 关键词 PostgreSQL高级数据类型, JSONB, 数组类型, PostGIS, 地理信息系统, NoSQL, 文档数据库, 空间数据, 数据库设计, PostgreSQL扩展 摘要 PostgreSQL的高级数据类型是其区别于传统关系数据库的核心…

Acrobat DC v25.001 最新专业版已破,像word一样编辑PDF!

在数字化时代&#xff0c;PDF文件以其稳定性和通用性成为了文档交流和存储的热门选择。无论是阅读、编辑、转换还是转曲&#xff0c;大家对PDF文件的操作需求日益增加。因此&#xff0c;一款出色的PDF处理软件不仅要满足多样化的需求&#xff0c;还要通过简洁的界面和强大的功能…

桥 接 模 式

在玩游戏的时候我们常常会遇到这样的机制&#xff1a;我们可以随意选择不同的角色&#xff0c;搭配不同的武器。这时只有一个抽象上下文的策略模式就不那么适用了&#xff0c;因为一旦我们使用继承的方式&#xff0c;武器和角色总有一方会变得难以扩展。这时&#xff0c;我们就…

基于 Flink+Paimon+Hologres 搭建淘天集团湖仓一体数据链路

摘要&#xff1a;本文整理自淘天集团高级数据开发工程师朱奥老师在 Flink Forward Asia 2024 流式湖仓论坛的分享。内容主要为以下五部分&#xff1a; 1、项目背景 2、核心策略 3、解决方案 4、项目价值 5、未来计划 01、项目背景 1.1 当前实时数仓架构 当前的淘天实时架构是从…

多杆合一驱动城市空间治理智慧化

引言&#xff1a;城市“杆林困境”与智慧化破局 走在现代城市的街道上&#xff0c;路灯、监控、交通信号灯、5G基站等杆体林立&#xff0c;不仅侵占公共空间&#xff0c;更暴露了城市治理的碎片化问题。如何让这些“沉默的钢铁”升级为城市的“智慧神经元”&#xff1f;答案在…

用QT写一个车速表

主要包含以下绘制步骤&#xff1a; 1、绘制画布&#xff1a; /** 绘制画布 */ void Widget::initCanvas(QPainter &painter) {//消除锯齿painter.setRenderHint(QPainter::Antialiasing,true);//设置底色painter.setBrush(QColor(0,0,0));painter.drawRect(rect());//平移…

数控技术应用理实一体化平台VR实训系统

::产品概述:: 目前我国本科类院校学生普遍存在的问题就是缺少对实际工作的了解&#xff0c;一直在学习相关专业的理论知识&#xff0c;对社会的相关企业的用人情况不了解。这也就直接导致了毕业的学生和社会上的用人单位需求有点脱节&#xff0c;这也是由于我国的现行本科教育侧…

C# 将HTML文档、HTML字符串转换为图片

在.NET开发中&#xff0c;将HTML内容转换为图片的需求广泛存在于报告生成、邮件内容存档、网页快照等场景。Free Spire.Doc for .NET作为一款免费的专业文档处理库&#xff0c;无需Microsoft Word依赖&#xff0c;即可轻松实现这一功能。本文将深入解析HTML文档和字符串转图片两…

界面控件DevExpress WinForms v24.2新版亮点:富文本编辑器功能全新升级

DevExpress WinForms拥有180组件和UI库&#xff0c;能为Windows Forms平台创建具有影响力的业务解决方案。DevExpress WinForms能完美构建流畅、美观且易于使用的应用程序&#xff0c;无论是Office风格的界面&#xff0c;还是分析处理大批量的业务数据&#xff0c;它都能轻松胜…

华为云Flexus+DeepSeek征文|华为云 Flexus X 加速 Dify 平台落地:高性能、低成本、强可靠性的云上选择

目录 前言 1 一键部署 Dify 平台的完整步骤 1.1 选择模板 1.2 参数配置 1.3 资源栈设置 1.4 配置确认与部署 2 Flexus X 服务器的技术优势 2.1 柔性算力随心配 2.2 一直加速一直快 2.3 越用越省降本多 2.4 安全可靠更放心 3 Flexus X 在 Dify 解决方案中的性能体验…