Compose 编程思想
本教程共 100 篇 · 第 42 篇 · 更新于 2026-07-28 · 约 8 分钟阅读
42. Compose 编程思想
本节目标:理解声明式 UI 和命令式 UI 的本质区别,搞懂可组合函数的特性、重组是怎么回事,为后续 Compose 学习打下思维基础。
从命令式到声明式
先打个比方。命令式 UI 像做饭时一步步下指令:先切菜、再起锅、放油、倒菜、翻炒、加盐。你精确控制每一步,锅里的状态被你反复修改。
声明式 UI 像点外卖:你只要告诉系统「我要一份番茄炒蛋」,至于怎么做、怎么翻炒,系统自己搞定。你描述的是「最终要什么样」,不是「怎么一步步做到」。
传统 Android View 体系是命令式的。你用 XML 写布局,代码里用 findViewById 拿到控件,然后 setText()、setVisibility() 一个个改。数据一变,你得记得手动更新所有相关控件,漏了一个界面就不一致。
Jetpack Compose 是声明式的。你用 Kotlin 函数描述界面长什么样,数据变了,框架自动重新计算并更新界面。你不用手动操控控件,只需描述「这个数据对应这个界面」。
可组合函数
Compose 的核心是可组合函数(Composable Function)。用 @Composable 注解标记一个普通 Kotlin 函数,它就能描述界面:
@Composable
fun Greeting(name: String) {
Text("你好,$name!")
}
这个函数接受数据(name),发出界面元素(Text)。它有几个关键特性:
- 有
@Composable注解:告诉 Compose 编译器这是界面函数。 - 接受数据作为参数:数据描述了界面应该长什么样。
- 调用其他可组合函数:通过组合简单组件构建复杂界面。
- 没有返回值:函数描述界面状态,不返回控件对象。
- 快速、幂等、无副作用:同样的输入永远得到同样的结果。
声明式的工作流
在命令式世界里,你改数据后要手动更新视图。在声明式世界里,流程是这样的:
- 组合(Composition):Compose 首次执行你的可组合函数,生成界面描述。
- 状态变化:用户点击或数据更新,触发状态改变。
- 重组(Recomposition):Compose 用新数据重新执行可组合函数,更新界面。
数据变化 -> 重组 -> 界面更新
关键是第三步:你不需要手动告诉界面「这里改了,那里改了」。 只要数据变了,Compose 自动找到受影响的部分重新执行。
重组(Recomposition)
重组是 Compose 的核心机制,也是最容易踩坑的地方。
打个比方:重组像考试改卷。老师(Compose)不需要把整份卷子重新批改一遍,只改你涂改过的那道题。Compose 也会智能跳过参数没变的函数,只重新执行参数有变化的部分。
@Composable
fun ClickCounter(clicks: Int, onClick: () -> Unit) {
Button(onClick = onClick) {
Text("点了 $clicks 次")
}
}
每次点击,clicks 变了,Text 会被重组。但如果旁边有个显示其他数据的 Text,它不会被重新执行—因为它的参数没变。
Warning重组有几个重要特性,写代码时必须记住:
- 重组会跳过尽可能多的内容:只有参数变化的函数才重组。
- 重组是乐观的:Compose 认为参数会变完就停,如果参数又变了,可能取消当前重组重新开始。
- 可组合函数可能非常频繁地执行:动画时可能每帧都执行。
- 可组合函数可以并行执行:Compose 可能在多线程上同时执行不同的可组合函数。
- 可组合函数可以按任意顺序执行:不能假设先写的先执行。
无副作用原则
因为重组的这些特性,可组合函数必须没有副作用。副作用是指对函数外部世界产生的影响,比如:
- 修改全局变量
- 写文件
- 更新 ViewModel 里的数据
- 调用
Random()
为什么?因为 Compose 可能跳过、取消、重试、并行执行你的函数。如果函数有副作用,执行次数和时机都不可预测,界面就会出 bug。
// 错误:在可组合函数里修改局部变量计数
@Composable
fun BadExample(items: List<String>) {
var count = 0 // 别这么写
Column {
for (item in items) {
Text(item)
count++ // 危险!重组时 count 会乱掉
}
Text("共 $count 项")
}
}
// 正确:直接用数据计算
@Composable
fun GoodExample(items: List<String>) {
Column {
for (item in items) {
Text(item)
}
Text("共 ${items.size} 项")
}
}
Tip
- 如果确实需要执行副作用(比如网络请求、文件读写),应该通过回调触发(如
onClick),或使用专门的副作用 API(LaunchedEffect等,第 54 章会讲),而不是直接在函数体里写。
动态内容
可组合函数是纯 Kotlin 代码,所以你拥有语言的全部能力。可以用 if 控制显示,用 for 循环生成列表,调用辅助函数:
@Composable
fun GreetingList(names: List<String>) {
Column {
if (names.isEmpty()) {
Text("暂无数据")
} else {
for (name in names) {
Text("你好,$name!")
}
}
}
}
这是 Compose 相比 XML 的巨大优势:界面逻辑直接用 Kotlin 写,不用数据绑定、不用模板语言,所见即所得。
Compose 的优势
总结一下 Compose 为什么好:
- 代码简洁:一个函数描述一个组件,比 XML + Java/Kotlin 两套文件清晰。
- 类型安全:编译期就能发现错误,不像 XML 的字符串引用运行时才报。
- 没有手动更新:数据变了界面自动更新,不用记得调
setText。 - 可复用:函数组合天然支持复用,比自定义 View 简单太多。
- Kotlin 原生:if/for/when 直接用,界面逻辑和业务逻辑同一种语言。
Note
- Compose 不是要消灭 View 体系。现有项目可以逐步迁移,新旧可以共存(第 55 章会讲互操作)。但新项目推荐直接用 Compose。
小结
思维转变是学 Compose 的第一步。记住:你不再「操控控件」,而是「描述界面和数据的关系」。可组合函数要快、幂等、无副作用,把副作用交给回调和专门的 API 处理。理解了重组机制,后面写代码就不容易踩坑了。