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 在保持声明式的同时,安全地与命令式世界交互。