ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Jetpack Compose 状态提升(State Hoisting)完全指南

Jetpack Compose 状态提升(State Hoisting)完全指南 Jetpack Compose 状态提升State Hoisting完全指南基于官方 Codelab 整理专为 Android 开发者打造一、什么是状态提升一句话定义状态提升是一种设计模式将子组件的状态移动到父组件中让父组件成为单一事实来源。核心思想❌ 错误做法状态藏在子组件里 子组件自己记状态 → 父组件不知道 → 无法控制 ✅ 正确做法状态提升到父组件 父组件记状态 → 传给子组件 → 子组件只负责显示二、从问题出发为什么需要状态提升场景点击按钮展开/折叠内容❌ 问题代码状态在子组件内部ComposablefunGreeting(name:String){// 问题状态在子组件内部varexpandedfalse// 普通变量改了也不会重组Surface(colorMaterialTheme.colorScheme.primary,modifierModifier.padding(vertical4.dp,horizontal8.dp)){Row(modifierModifier.padding(24.dp)){Column(modifierModifier.weight(1f)){Text(textHello, )Text(textname)// 根据 expanded 显示/隐藏内容if(expanded){Text(text这是隐藏的内容)}}ElevatedButton(onClick{expanded!expanded// 改了也不会触发重组}){Text(Show more)}}}}问题在哪expanded是普通变量不是State即使改成mutableStateOf状态也在子组件内部父组件无法控制它展开还是折叠无法在多个地方同步这个状态✅ 解决方案状态提升// 步骤 1子组件接收状态 事件回调ComposablefunGreeting(name:String,expanded:Boolean,// 状态从父组件传来onExpandedChange:(Boolean)-Unit// 事件回调给父组件){Surface(colorMaterialTheme.colorScheme.primary,modifierModifier.padding(vertical4.dp,horizontal8.dp)){Row(modifierModifier.padding(24.dp)){Column(modifierModifier.weight(1f)){Text(textHello, )Text(textname)if(expanded){Text(text这是隐藏的内容)}}ElevatedButton(onClick{onExpandedChange(!expanded)// 通知父组件改状态}){Text(if(expanded)Show lesselseShow more)}}}}// 步骤 2父组件持有状态ComposablefunMyApp(){// 状态在这里父组件是单一事实来源varexpandedbyremember{mutableStateOf(false)}Greeting(nameAndroid,expandedexpanded,// 向下传递状态onExpandedChange{expandedit}// 接收事件更新状态)}三、状态提升的完整流程图┌─────────────────────────────────────────────────────────┐ │ 父组件 (MyApp) │ │ ┌─────────────────────────────────────────────────┐ │ │ │ var expanded by remember { mutableStateOf(false) } │ │ └─────────────────────────────────────────────────┘ │ │ │ │ │ 状态向下传递 │ 事件向上传递 │ │ expanded ───────┼────── onExpandedChange │ │ │ │ └─────────────────────────┼────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────┐ │ 子组件 (Greeting) │ │ ┌─────────────────────────────────────────────────┐ │ │ │ fun Greeting( │ │ │ │ expanded: Boolean, │ │ │ │ onExpandedChange: (Boolean) - Unit │ │ │ │ ) │ │ │ └─────────────────────────────────────────────────┘ │ │ │ │ │ 只读状态 │ 只负责通知事件 │ │ │ │ └─────────────────────────────────────────────────────────┘四、状态提升的三大好处1️⃣ 可复用Reusable子组件不依赖固定状态拿到什么显示什么// 同一个 Greeting可以用在不同地方ComposablefunScreen1(){varexpandedbyremember{mutableStateOf(true)}// 默认展开Greeting(A,expanded,{expandedit})}ComposablefunScreen2(){varexpandedbyremember{mutableStateOf(false)}// 默认折叠Greeting(B,expanded,{expandedit})}2️⃣ 可测试Testable可以单独测试子组件传不同的状态值PreviewComposablefunGreetingExpandedPreview(){Greeting(Test,expandedtrue,onExpandedChange{})}PreviewComposablefunGreetingCollapsedPreview(){Greeting(Test,expandedfalse,onExpandedChange{})}3️⃣ 可控制Controllable父组件掌握数据逻辑清晰多个组件可以共享状态ComposablefunParentScreen(){varexpandedbyremember{mutableStateOf(false)}Column{// 两个 Greeting 共享同一个状态Greeting(A,expanded,{expandedit})Greeting(B,expanded,{expandedit})// 父组件可以强制控制Button(onClick{expandedtrue}){Text(全部展开)}}}五、状态提升口诀状态跟谁强相关就放在谁最近的地方。单个组件用 → 放在组件内部多个组件共享 → 提升到最近的共同父组件涉及业务逻辑 → 提升到 ViewModel决策树状态需要被谁读取/修改 │ ├── 只有这一个组件 → 保持在内部 │ └── 用 remember { mutableStateOf() } │ ├── 多个组件需要 → 提升到共同父组件 │ └── 父组件用 remember { mutableStateOf() } │ └── 涉及业务逻辑/跨屏幕 → 提升到 ViewModel └── ViewModel 用 mutableStateOf / StateFlow六、实战完整展开列表示例最终代码classMainActivity:ComponentActivity(){overridefunonCreate(savedInstanceState:Bundle?){super.onCreate(savedInstanceState)setContent{BasicsCodelabTheme{MyApp(modifierModifier.fillMaxSize())}}}}ComposablefunMyApp(modifier:ModifierModifier){Surface(modifiermodifier,colorMaterialTheme.colorScheme.background){Column{Greeting(nameAndroid)Greeting(nameCompose)Greeting(nameState)}}}ComposablefunGreeting(name:String,expanded:Booleanfalse,onExpandedChange:(Boolean)-Unit{}){Surface(colorMaterialTheme.colorScheme.primary,modifierModifier.padding(vertical4.dp,horizontal8.dp)){Row(modifierModifier.padding(24.dp)){Column(modifierModifier.weight(1f)){Text(textHello, )Text(textname)if(expanded){Text(text这是$name的隐藏内容,modifierModifier.padding(top8.dp))}}ElevatedButton(onClick{onExpandedChange(!expanded)}){Text(if(expanded)Show lesselseShow more)}}}}Preview(showBackgroundtrue,widthDp320)ComposablefunGreetingPreview(){BasicsCodelabTheme{MyApp()}}七、常见问题 FAQQ1每个 Greeting 都要独立控制展开/折叠怎么办ComposablefunMyApp(){// 用 Map 存储每个 name 的展开状态varexpandedMapbyremember{mutableStateOf(mapOfString,Boolean())}Column{listOf(Android,Compose,State).forEach{name-Greeting(namename,expandedexpandedMap[name]?:false,onExpandedChange{expandedMapexpandedMap(nametoit)})}}}Q2状态提升后代码好啰嗦有没有简化写法有用 Kotlin 的by委托// 啰嗦写法varcountremember{mutableStateOf(0)}Button(onClick{count.value}){Text(${count.value})}// 简洁写法varcountbyremember{mutableStateOf(0)}Button(onClick{count}){Text($count)}Q3什么时候不应该状态提升当状态只在一个组件内部使用且不需要外部控制时ComposablefunChatBubble(message:Message){// 这个状态只在内部用不需要提升varshowDetailsbyrememberSaveable{mutableStateOf(false)}ClickableText(textAnnotatedString(message.content),onClick{showDetails!showDetails})if(showDetails){Text(message.timestamp)}}八、总结概念说明状态提升把状态从子组件移到父组件单一事实来源状态只有一个地方管理避免多份副本向下传递父组件把状态传给子组件向上传递子组件通过回调通知父组件改状态核心模式// 父组件持有状态varstatebyremember{mutableStateOf(initialValue)}// 子组件接收状态 事件回调ChildComposable(statestate,onStateChange{stateit})参考资料Jetpack Compose 基础知识 Codelab状态提升官方文档Jetpack Compose 中的状态 Codelab最后更新2026-05-19整理自 Android 官方 Codelab
返回列表