Compose Runtime
Compose 底层 · androidx.compose.runtime
Compose 副作用1.13.0-alpha01
在 LaunchedEffect、DisposableEffect、SideEffect 和 rememberCoroutineScope 之间选择。
最后更新 2026-08-01
先按生命周期选择
| 需求 | API |
|---|---|
| 进入 Composition 启动协程,key 变化时重启 | LaunchedEffect |
| 注册资源并在离开或 key 变化时释放 | DisposableEffect |
| 每次成功应用 Composition 后同步外部对象 | SideEffect |
| 点击等事件中启动、离开 Composition 自动取消的协程 | rememberCoroutineScope |
| 用普通代码做一次性初始化(非协程) | remember + 延迟初始化 |
LaunchedEffect:按 key 启动协程
LaunchedEffect(userId) {
viewModel.load(userId)
}- 进入 Composition 启动块内协程;key 变化时取消旧协程并重启。
- key 为空(
LaunchedEffect(Unit))只在首次进入时执行。 - 数据加载、debounce、轮询都适合。
不要把不断变化的值当 key:
// ❌ text 每次输入都变 → 每次都重启 → 加载被反复取消
LaunchedEffect(text) { viewModel.search(text) }
// ✅ 用 snapshotFlow + debounce,或 rememberUpdatedState 读最新值
LaunchedEffect(Unit) {
snapshotFlow { text }
.debounce(300)
.collect { viewModel.search(it) }
}DisposableEffect:成对释放
DisposableEffect(sensorManager, listener) {
sensorManager.registerListener(listener)
onDispose {
sensorManager.unregisterListener(listener)
}
}注册和释放放同一个 effect,才能覆盖:
- key 改变(先 dispose 旧的再建新的)
- 条件分支移除(
if变 false) - 页面销毁(离开 Composition)
rememberUpdatedState:读最新值不重启
val currentUserId by rememberUpdatedState(userId)
LaunchedEffect(Unit) {
while (isActive) {
// 用 currentUserId,而不是闭包捕获的旧 userId
api.poll(currentUserId)
delay(5000)
}
}协程块捕获的是启动时的值;需要读最新值但不想因此重启时用 rememberUpdatedState。
SideEffect:应用后同步外部
SideEffect {
analytics.setScreenName("profile")
}每次重组成功应用到 UI 后执行——适合同步非 Compose 状态(如系统 API、第三方 SDK)。
rememberCoroutineScope:事件驱动的协程
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
// 事件触发的动画/请求
snackbarHostState.showSnackbar("已保存")
}
})- 点击等事件里启动协程,离开 Composition 自动取消。
- 不要用”改变布尔 key 触发 LaunchedEffect”代替事件——那是反模式。
常见陷阱
- 副作用写进函数体:重组重复执行(网络、注册)。
- LaunchedEffect key 是变化值:无限重启。
- DisposableEffect 不配对:泄漏资源。
- 协程捕获旧值:长循环里闭包值过期(用 rememberUpdatedState)。
- 事件用 effect 触发:应该用 rememberCoroutineScope。
复制即用
@Composable
fun ProfileScreen(userId: String, viewModel: ProfileViewModel = viewModel()) {
// 加载(按 userId 重启)
LaunchedEffect(userId) { viewModel.load(userId) }
// 轮询(不因 userId 变化重启,但读最新值)
val currentId by rememberUpdatedState(userId)
LaunchedEffect(Unit) {
while (true) {
viewModel.refreshStatus(currentId)
delay(30_000)
}
}
// 注册资源
DisposableEffect(Unit) {
val listener = viewModel.registerCallback()
onDispose { viewModel.unregisterCallback(listener) }
}
// 事件
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch { viewModel.save() }
}) { Text("保存") }
}要点
LaunchedEffect(key)按 key 启动/重启协程;key 用稳定身份。DisposableEffect注册 +onDispose释放成对出现。rememberUpdatedState让长协程读最新值;SideEffect同步外部。rememberCoroutineScope处理事件;副作用绝不写进函数体。