Compose 副作用 · AndroidX 源码指南
AAndroidX 源码指南
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 处理事件;副作用绝不写进函数体。

相关页面