SuperAPI AI 工具集

compose-side-effects

编程与开发

查看项目

更新于 10-16 05:38

技能介绍

用途

本技能适用于编写或审查使用 Jetpack Compose 的代码,尤其是涉及副作用(side effects)的场景。其核心原则是:Composable 函数体只负责描述 UI,它们可能被重组、跳过或放弃;任何改变外部世界的工作都应放入生命周期与任务相匹配的 effect API 中。

主要能力

技能围绕以下 API 给出选择与使用指导:

  • SideEffect:在每次成功重组后,将 Compose 状态发布给非 Compose 代码。
  • DisposableEffect(keys...):注册/注销监听器、回调、观察者或资源。
  • LaunchedEffect(keys...):运行挂起、延迟或带 key 的一次性工作。
  • rememberCoroutineScope():从用户事件回调中启动挂起工作。
  • snapshotFlow { ... }:在协程内将 Compose 快照读取转换为 Flow,需配合 LaunchedEffect 使用。

技能强调“选择最小的 effect”,即根据具体需求选用最合适的 API,而不是一律使用 LaunchedEffect。

使用方式

Effect keys

Keys 定义重启身份。当任一 key 变化时,旧 effect 被取消/销毁,新 effect 启动。应使用稳定、语义化的 key,例如 userId、screenId、lifecycleOwner、focusRequester,即 effect 生命周期所跟随的对象。不要使用宽泛对象(如 state、viewModel)当只有单个属性重要时;也不要添加变化的 lambda 作为 key,除非确实希望每次 lambda 变化都重启。

避免陈旧捕获

对于不应重启但需要最新回调或值的长期运行 effect,使用 rememberUpdatedState。典型场景包括:超时或启动画面 effect 不应因 onTimeout 变化而重启,但应调用最新回调;生命周期观察者应保持注册到同一 owner,但调用最新的 onStart/onStop lambda;长期运行的收集器应保持其收集生命周期,但调用最新事件处理器。

注意:不要用 rememberUpdatedState 来回避选择正确的 key。如果变化的值应当重启工作,就应把它设为 key。rememberUpdatedState 也不会让渲染状态“不重组”;如果 UI 需要显示变化的值,应在组合中读取普通 State,或使用延迟读取模式。

收集 Flow

使用 LaunchedEffect 处理副作用/事件流:snackbar、导航事件、分析事件、焦点命令等,即每次发射都触发命令式工作的流。不要为了修改本地状态而命令式地收集渲染状态;UI 状态应在状态持有者附近收集,并将普通值传入 UI composable。在 Android 上,优先使用生命周期感知的收集方式;在没有生命周期感知 API 的目标上使用 collectAsState()。

对于 Compose 状态读取,使用 snapshotFlow,并注意 snapshotFlow { ... }.map { ... } 若没有终端 collect 则不会执行任何操作。

用户事件

当点击或手势启动挂起工作时,使用 rememberCoroutineScope()。避免仅为触发 LaunchedEffect 而设置“事件标志”状态——点击本身就是事件。

注册与清理

使用 DisposableEffect 进行成对的 setup/teardown。每条注册路径都应有匹配的 onDispose 清理路径。

适用场景

  • 编写新的 Compose 副作用代码。
  • 审查现有 Compose 代码中的 effect 使用是否合理。
  • 排查因 key 选择不当、陈旧捕获、缺少清理导致的 bug。
  • 处理 Flow 收集、用户事件启动协程、监听器注册与注销等常见任务。

限制

技能内容为 Compose 副作用处理的编码规范与模式指导,不涉及具体项目架构的完整设计。关于状态持有者与 UI 拆分、collectAsStateWithLifecycle() / collectAsState() 的预览友好接线等内容,技能明确指向 compose-state-holder-ui-split,不在本技能中重复。帧率相关值的延迟读取模式指向 compose-state-deferred-reads。

常见错误与审查红旗

技能列出了常见错误及修复方式,例如:在 composable 函数体中直接发起网络请求(通常应移到 ViewModel/状态持有者,仅在 UI 拥有的带 key 工作时使用 LaunchedEffect);从 composable 函数体写入分析属性(应在每次成功重组后发布时使用 SideEffect);从 composable 函数体记录曝光/事件(应在该 key 下运行一次时使用 LaunchedEffect(key));LaunchedEffect(Unit) 捕获变化的 id(应按 id 设 key,或使用 rememberUpdatedState 使其不重启);长期 effect 在重组后调用旧回调(陈旧捕获);LaunchedEffect(state) 重启过于频繁(按具体属性设 key);LaunchedEffect(...) 中调用非挂起 setter(通常应使用 SideEffect);在 LaunchedEffect 中添加监听器但无清理(应使用 DisposableEffect);通过设置 shouldShowSnackbar = true 从点击启动(应在点击回调中使用 rememberCoroutineScope())。

审查红旗包括:对 composable 函数体中的代码说“这只运行一次”;在参数会变化的函数中使用 LaunchedEffect(Unit);effect 内的 flow 链没有终端收集;effect 的 key 是为了消除 lint 警告而非建模生命周期;长期 effect 使用回调 lambda 时既没有 key 也没有 rememberUpdatedState。