Compose 声明式编程 状态  数据传递模式 Compose 声明式编程 状态 数据传递模式Compose 里"哪种 API 存哪种状态"很容易选错——选轻了状态被旋转吃掉,选重了在该用remember的地方架个 ViewModel。这份文档把项目里已经在用的几种模式按"作用域 + 异步性"两条轴排一下,给出选型决策、样例代码和踩过的坑。0. 一句话决策表我要存的 / 做的用什么按钮按下、对话框开关、tab 选中等纯视觉态remember { mutableStateOf(...) }旋转 / 进后台要保住的 UI 状态rememberSaveable由其他可观察状态算出来的派生值derivedStateOf一次性异步拉取的结果(屏幕私有)produceState监听某些 key 变化跑副作用LaunchedEffect(key)点击事件里要启动协程rememberCoroutineScope复杂状态机 / 跨重组 / 需要测试 / 需要分页ViewModel+StateFlow多屏共享 / 活动级单例Activity 作用域ViewModel沿组合树隐式传递(导航回调、当前用户身份)CompositionLocalLocalBack/LocalNavigationTo/LocalCurrentUserId把多个 list 参数收成一个传给子 Composabledata class包装让子 Composable 不关心数据形态,只渲染tabContent: @Composable () - Unitslot lambda1. 作用域 × 异步性 四象限┌──────────────────────────┬──────────────────────────┐ │ Composition 内(短) │ ViewModel 内(长) │ ┌───────────────────┼──────────────────────────┼──────────────────────────┤ │ 同步 / 即时 │ remember / mutableStateOf│ ViewModel 普通字段 │ │ │ rememberSaveable │ │ ├───────────────────┼──────────────────────────┼──────────────────────────┤ │ 异步 / 协程 │ LaunchedEffect │ StateFlow + viewModelScope│ │ │ produceState │ .launch │ │ │ rememberCoroutineScope │ │ └───────────────────┴──────────────────────────┴──────────────────────────┘左侧生命周期是 “Composable 在屏 / 离屏” —— 旋转、Tab 切走 + 切回都会重建。右侧生命周期是 ViewModelStore —— 跟 Activity 走,旋转不丢,离开屏幕也保留(直到 ViewModelStore 清理)。2. 各模式详解2.1remember { mutableStateOf(...) }—— 最轻量的本地状态varsheetOpenbyremember{mutableStateOf(false)}Button(onClick={sheetOpen=true}){Text("Open")}if(sheetOpen){BottomSheet(onDismiss={sheetOpen=false})}适合:纯 UI 视觉状态(开关、动画 trigger、tab index 等);与数据层无关。不适合:想让旋转后还保留 → 用rememberSaveable。想让多个 composable 共享 → 提升到父组件,或上 ViewModel。想存 List / 复杂对象 → 想清楚是不是该上 StateFlow。经典陷阱:把 props 当 seed 写进 remember:// ❌ 错的——props.followedByMe 变化后 isFollowing 不会更新varisFollowingbyremember{mutableStateOf(props.followedByMe)}// ✅ 加 key——props 变化时 remember 块重建,按新 props seedvarisFollowingbyremember(props.followedByMe){mutableStateOf(props.followedByMe)}第二种写法在OtherScreen用produceState时承担过 follow 按钮的状态管理。2.2rememberSaveable—— 旋转保活的本地状态varisLandscapebyrememberSaveable{mutableStateOf(false)}适合:旋转 / 进后台后还想保住的纯 UI 状态。限制:只能存Parcelable/Serializable/ 内置基本类型。自定义类要写Saver。跨进程 kill 也能恢复(依赖 SavedInstanceState)。反例 / 坑:用rememberSaveable存"一次性事件完成"flag(典型:“已经跳过页”)——见anti-pattern #1。2.3derivedStateOf—— 派生状态,避免抖动valshouldLoadMorebyremember(pagerState){derivedStateOf{valtotal=state.items.size total0pagerState.settledPage=total-PRELOAD_AHEADstate.hasMore!state.isLoadingMore}}关键性能特性:只有当内部读到的可观察状态计算出的结果发生变化,下游订阅者才会被通知。换句话说,pager 滑动每帧 offset 抖动不会让shouldLoadMore重新通知;只有 false→true / true→false 翻转才会。适合:监听多个 state 算出一个 boolean / 简化值。跟LaunchedEffect