Compose 对比:SideEffect / LaunchedEffect / DisposableEffect

5 阅读2分钟

1. 一句话总纲

  • SideEffect:每次重组后,同步执行一段代码
  • LaunchedEffect:key 变化时,启动协程做异步任务
  • DisposableEffect:带清理 / 销毁的 effect,离开重组范围时自动执行收尾

2. SideEffect

核心

每次重组成功后 → 同步执行 → 无 key → 不能 suspend

特点

  • 每次重组必跑
  • 同步执行,不能 delay、不能挂起
  • 没有 key,无法 “只执行一次”
  • 适合极轻量操作:埋点、日志、更新外部对象

典型场景

SideEffect {
    // 埋点/日志
    Analytics.log("show_page")
    // 更新外部非 compose 状态
    audioView.setAmplitude(amp)
}

禁忌

  • 不要更新 State(会循环重组)
  • 不要做耗时、异步操作

2. LaunchedEffect

核心

key 变化时 → 启动协程 → 可 suspend → 可取消重启

特点

  • 依赖 key,key 不变就不重启
  • 内部是协程作用域,可以:delay、launch、await
  • 重组时如果 key 没变,不会重新执行
  • 离开组合时自动取消协程

典型场景

LaunchedEffect(userId) {
    // 网络请求
    val data = api.getUser(userId)
    // 动画
    animateTo(1f)
    // 定时任务
    while(true) {
        delay(300)
        updateWave()
    }
}

常用技巧

  • LaunchedEffect(Unit) = 只执行一次(类似 onCreate)
  • LaunchedEffect(xxx) = xxx 变化时重新执行

3. DisposableEffect

核心

带清理 / 解绑的 effect → 进入执行 onEnter + 离开 /key 变化执行 onDispose

特点

  • 和 LaunchedEffect 一样受 key 控制
  • 必须实现 onDispose {}
  • 适合:注册 / 反注册、订阅 / 取消订阅、创建 / 销毁

典型场景

DisposableEffect(lifecycle) {
    // 进入时:注册
    lifecycle.addObserver(observer)

    // 离开/key 变化时:清理
    onDispose {
        lifecycle.removeObserver(observer)
    }
}

最常用场景

  • 注册广播
  • 传感器监听
  • 自定义 View 回调绑定 / 解绑
  • 播放、录音资源管理

4. 三张表彻底分清(面试 + 开发必备)

① 执行时机

Effect触发时机是否受控
SideEffect每次重组后❌ 不受控
LaunchedEffectkey 变化 / 首次进入✅ key 控制
DisposableEffectkey 变化 / 首次进入✅ key 控制

② 能力限制

Effect可 suspend可清理同步 / 异步
SideEffect❌ 不可❌ 无同步
LaunchedEffect✅ 可以❌ 无协程异步
DisposableEffect❌ 不可(但可配合协程)✅ onDispose同步

③ 最适合场景

Effect典型业务
SideEffect埋点、日志、更新外部对象
LaunchedEffect请求接口、动画、轮询、定时任务
DisposableEffect注册 / 反注册、订阅、资源释放

5. 一套代码对比(秒懂)


@Composable
fun WaveScreen(amp: Float) {

    // 1. 每次重组都跑:同步副作用
    SideEffect {
        Log.d("wave", "重组了")
    }

    // 2. key 变化跑协程:轮询振幅
    LaunchedEffect(Unit) {
        while(true) {
            delay(300)
            // 更新波形
        }
    }

    // 3. 带清理:注册录音监听
    DisposableEffect(Unit) {
        val callback = RecordCallback()
        recorder.register(callback)

        onDispose {
            recorder.unregister(callback)
        }
    }
}

6. 终极选择口诀

  • 只要轻量同步、每次重组都要 → SideEffect
  • 只要异步 / 协程 / 定时 / 请求 → LaunchedEffect
  • 只要注册订阅、必须手动清理 → DisposableEffect