Jetpack Compose之“副作用”的讲解

Compose附带效应的核心概念

在 Jetpack Compose 中,"附带效应"(Side Effects)是一个核心概念,用于处理 "在 Compose 的声明式 UI 之外发生的、与 UI 状态同步但不直接参与 UI 渲染的操作"。

📌 一句话定义:

附带效应 = 在 Composable 函数中执行的、会影响外部世界(如启动协程、修改非 Compose 状态、调用系统 API)的操作。

为什么需要"附带效应"?

Compose 的核心是纯函数 + 响应式状态驱动 UI 。理想情况下,@Composable 函数应该是:

  • 无副作用(Pure):只根据输入状态生成 UI,不修改外部状态。
  • 可重入(Re-entrant):可能被多次、任意顺序调用。

但现实开发中,我们经常需要:

  • 启动网络请求
  • 记录埋点日志
  • 修改 ViewModel 中的状态
  • 请求权限
  • 播放音效
  • 跳转页面

这些操作不能随意执行 (比如每次重组都调一次API就炸了),必须 精确控制执行时机和次数

Jetpack Compose 提供的主要附带效应 API

🧪 常见使用场景详解

1️⃣ LaunchedEffect ------ 最常用!

在协程中执行逻辑,仅当 key 变化时重新启动。

✅ 场景:页面加载数据

kotlin 复制代码
@Composable
fun UserProfileScreen(userId: String, viewModel: UserViewModel) {
    // 当 userId 变化时,重新加载用户数据
    LaunchedEffect(userId) {
        viewModel.loadUser(userId)
    }

    val user by viewModel.userState.collectAsState()
    // ... 显示 UI
}

✅ 场景:收集 Flow(替代 LiveData)

kotlin 复制代码
LaunchedEffect(Unit) {
    viewModel.uiState.collect { state ->
        when (state) {
            is Success -> showSuccess(state.data)
            is Error -> showError(state.message)
        }
    }
}

⚠️ 注意:不要写 LaunchedEffect(true),应使用 Unit 或具体 key。

2️⃣ DisposableEffect ------ 需要清理的资源

类似 android 中Activity的生命周期 onResume / onDestroy,进入时执行,离开时清理。

✅ 场景:注册广播接收器

kotlin 复制代码
@Composable
fun BatteryStatusScreen() {
    var batteryLevel by remember { mutableStateOf(0f) }

    DisposableEffect(Unit) {
        val receiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                batteryLevel = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0).toFloat()
            }
        }

        context.registerReceiver(receiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED))

        // 返回清理逻辑
        onDispose {
            context.unregisterReceiver(receiver)
        }
    }

    Text("Battery: ${batteryLevel}%")
}

✅ 场景:连接蓝牙设备、开启摄像头等。

3️⃣ rememberUpdatedState ------ 解决"闭包捕获旧值"问题

kotlin 复制代码
@Composable
fun TimerScreen(onTick: () -> Unit) {
    val currentOnTick by rememberUpdatedState(onTick)

    LaunchedEffect(Unit) {
        while (true) {
            delay(1000)
            currentOnTick() // ← 总是最新版本
        }
    }
}

4️⃣ SideEffect ------ 每次重组都执行(谨慎!)

用于 将 Compose 状态同步到非 Compose 系统。

✅ 场景:设置非 Compose 组件的状态(如 Fragment)

kotlin 复制代码
@Composable
fun SyncWithFragment(title: String) {
    SideEffect {
        // 每次 title 变化,都更新 Activity 标题
        (LocalContext.current as? AppCompatActivity)?.supportActionBar?.title = title
    }
}

⚠️ 不要在这里做耗时操作或修改 Compose State!会导致无限重组。

5️⃣ produceState ------ 封装异步数据加载

将任意异步操作(如 Retrofit 调用)转换为 State。

kotlin 复制代码
@Composable
fun loadUserProfile(userId: String): State<Resource<User>> {
    return produceState(initialValue = Resource.Loading()) {
        try {
            val user = api.getUser(userId)
            value = Resource.Success(user)
        } catch (e: Exception) {
            value = Resource.Error(e.message ?: "Unknown error")
        }
    }
}

// 使用
val userState by loadUserProfile("123")

🚫 常见错误 & 最佳实践

✅ 总结:什么时候用附带效应?

💡 核心思想:

附带效应 = "受控的副作用"。

它让 Compose 在保持声明式的同时,安全地与命令式世界交互。

相关推荐
liang_jy40 分钟前
Android View Tag
android
liang_jy1 小时前
Android 架构中的统一分发与策略路由
android·架构
scan7243 小时前
长期记忆存储在数据库里
android
xingpanvip3 小时前
星盘接口开发文档:星相日历接口指南
android·开发语言·前端·css·php·lua
儿歌八万首6 小时前
Jetpack Compose 实战:实现一个动态平滑折线图
android·折线图·compose
李艺为10 小时前
Fake Device Test作假屏幕分辨率分析
android·java
zh_xuan10 小时前
github远程library仓库升级
android·github
峥嵘life10 小时前
Android蓝牙停用绝对音量原理
android
czlczl2002092511 小时前
IN和BETWEEN在索引效能的区别
android·adb
Volunteer Technology12 小时前
ES高级搜索功能
android·大数据·elasticsearch