一句话定位
SideEffect 用来把 Compose 中的状态同步给「组合之外」的对象或系统 ------它在每次成功重组后执行,没有 key,也没有清理回调。
核心特征:
- 首次进入组合 + 后续每次真实发生重组时都会执行;
- 运行在主线程的 apply 阶段,同步执行;
- 没有
key参数,无法做到"只有某个值变化才执行"。
典型使用场景
1. 同步给 Presenter / 传统 View(混合开发最常见)
Compose 之外的对象不会参与重组,需要手动把状态推给它:
kotlin
class SearchPresenter {
private var keyword: String = ""
fun updateKeyword(value: String) { keyword = value }
}
@Composable
fun SearchContent(presenter: SearchPresenter) {
var keyword by rememberSaveable { mutableStateOf("") }
TextField(value = keyword, onValueChange = { keyword = it })
// 每次 keyword 引起重组后,同步给非 Compose 的 Presenter
SideEffect {
presenter.updateKeyword(keyword)
}
}
同理可用于同步给 AndroidView 里持有的老控件、地图 SDK、播放器实例等。
2. 调用平台 API 更新非 Compose 的 UI
kotlin
@Composable
fun DetailScreen(title: String, toolbar: Toolbar) {
SideEffect {
toolbar.title = title // 传统 View,而不是 Compose 的 TopAppBar
}
}
例如更新 Activity.title、Window 状态栏属性、外部 TabLayout 文案等。
3. 埋点 / 日志上报
把当前 UI 状态同步给外部分析系统:
kotlin
@Composable
fun ProductScreen(state: ProductUiState, analytics: Analytics) {
SideEffect {
analytics.updateContext(state.productId, state.tab)
}
}
⚠️ 注意:如果要做"每次进入页面只上报一次 "的曝光埋点,应该用 LaunchedEffect(Unit) 或 DisposableEffect,而不是 SideEffect(它会重复执行)。
与前面几个 Effect 的选择
| 需求 | 用什么 |
|---|---|
| 执行异步任务、请求数据、监听 Flow | LaunchedEffect(key) |
| 注册监听器 / 观察者,离开时需要释放 | DisposableEffect(key) |
| 每次重组后,把状态同步给组合之外的对象 | SideEffect |
| 缓存计算结果,不产生副作用 | remember / derivedStateOf |
判断口诀:
- 需要 协程 / 挂起 →
LaunchedEffect - 需要 注册 + 反注册 →
DisposableEffect - 只是 同步给外部对象 →
SideEffect - 只想 值变化时执行一次 → 用带 key 的
LaunchedEffect/DisposableEffect
注意事项
kotlin
// ❌ 反例
SideEffect {
scope.launch { repository.fetch() } // 异步任务请用 LaunchedEffect
_uiState.value = newState // 不要用副作用反向写 Compose 状态
analytics.track("click") // 每次重组都会触发,会重复上报
}
- 必须轻量、幂等:重组可能被跳过、合并,也可能因重试重复执行,不要依赖"刚好执行一次"。
- 不要做 IO / 阻塞 / 耗时操作:它运行在主线程,会拖慢重组。
- 不用于管理生命周期 :它没有
onDispose,组件离开组合时不会通知你。 - 块内读 state 不会建立重组依赖 :需要被观察的状态要在 composable 主体里读,再把结果带进
SideEffect。