获课:999it.top/27139/
# 真正理解“状态驱动UI”的人,才能成为Compose高手
## 从一道面试题说起
上周帮团队面试,我问了个看似简单的问题:
“用Compose写一个计数器,点击按钮数字加1。不用IDE,说说思路。”
来面试的是一位有两年Android经验的开发者。他脱口而出:
“定义一个`var count = 0`,按钮点击时`count++`,然后`Text(count.toString())`显示出来。”
我追问:“那界面会更新吗?”
他犹豫了:“会吧……我点了按钮啊。”
**这是Compose初学者最容易踩的坑,也是区分“会用”和“真懂”的分水岭。**
---
## 一、UI不是“画”出来的,是“算”出来的
传统XML开发里,UI是一幅静态的画。
我们要更新某个数字,得先找到那个TextView,然后调用`setText()`。这个过程像什么呢?**你指着画布上的一处说:这里,改成新数字。**
这很符合直觉——我直接操作UI,它就应该变。
**但Compose不这么想。**
在Compose的世界里,UI不是一幅被你反复修改的画,而是**一个函数根据当前状态计算出的结果**。
用公式表达就是:
```
UI = f(state)
```
这个`f`是你的Composable函数,`state`是数据状态。状态变了,函数重新执行,UI重新“算”一遍。
**你不去改UI,你只改数据。** 这个思维的转变,比学会任何Compose API都重要。
---
## 二、为什么新手总觉得Compose“不听话”
回到开头的计数器。
那位面试者的代码,在Compose里**界面不会更新**。为什么?
因为他定义了一个普通的Kotlin变量。Compose在执行函数时读取了这个变量的值(0),把它显示出来。但当变量变成1时,Compose**并不知道这个变化**。
它没被通知,就不会重新执行函数。
这就好比你告诉厨师:“客人点了鱼香肉丝。”厨师做好端上去。过了一会儿客人说:“我改宫保鸡丁了。”但你没告诉厨师,厨师还在厨房刷手机。
**不是Compose不听话,是它根本不知道需要动。**
那怎么让Compose知道呢?
```kotlin
var count by remember { mutableStateOf(0) }
```
`mutableStateOf`包了一层“可观察性”。当`count`变化时,它会主动通知所有用到它的Composable:“我变了,你们重新算一下。”
这就是状态驱动的核心机制:**状态可观察,UI自动响应。**
---
## 三、状态的“三六九等”,你分清楚了吗
很多人在Compose里用状态,全靠一种方式:`remember`。
但高手会用三种:
### 1. remember:单界面、不跨重组
适合临时UI状态,比如展开/折叠、当前输入框文本。界面重建就重置。
### 2. rememberSaveable:保命状态
适合应对屏幕旋转、进程回收。自动存入Bundle,绕过了remember的“健忘症”。
### 3. ViewModel:跨界面、跨生命周期
适合业务数据。即使界面关了又开,数据还在。搭配`collectAsState()`使用。
有个简单的选择题:
- 这个状态**只有这个界面关心**,且**重建不要紧** → `remember`
- 这个状态**只有这个界面关心**,但**重建不能丢** → `rememberSaveable`
- 这个状态**多个界面关心**,或**必须存到数据库/网络** → ViewModel
选对了,状态不会乱窜;选错了,bug悄悄埋下。
---
## 四、状态提升:把“糊涂”组件变“通透”
我在项目里见过这样的代码:
一个自定义的`ProfileCard`,内部自己维护着用户资料的状态,自己发起网络请求,自己显示加载中、成功、失败。
看起来很方便,对不对?
问题来了:**如果另一个页面也想复用这个卡片,但数据要从别的地方传过来呢?**
这个卡片“太自主”了——它不听外界的,只信自己。
解决方案是**状态提升(State Hoisting)**。把状态从组件内部“拎”出来,交给上层管理:
```kotlin
// 不推荐
@Composable
fun ProfileCard(userId: String) {
var user by remember { mutableStateOf(loadUser(userId)) }
// 显示用户信息
}
// 推荐
@Composable
fun ProfileCard(
user: User, // 状态从外部传入
onUserUpdate: (User) -> Unit // 事件回调
) {
// 只管显示
}
```
这背后是一条黄金法则:**谁需要修改状态,谁就应该持有状态。**
底层组件只负责“呈现”,不负责“决定”。这样的组件可预测、易测试、好复用。
---
## 五、重组不是洪水猛兽,滥用才是
很多人听说“Composable函数会频繁执行”,吓得把大量计算塞进`LaunchedEffect`或`derivedStateOf`。
其实**重组是正常的,慢的重组才是问题。**
Compose有一套智能跳过机制:如果一个参数没变,对应的UI部分就不会重新执行。
所以优化的核心不是“少重组”,而是**让重组发生在真正需要的时候**,并且**不要在重组时做昂贵操作**。
几个实用判断:
- 从ViewModel拿数据?用`collectAsState()`已经优化好了
- 列表项里做复杂计算?移到外面用`remember`缓存结果
- UI临时状态和业务状态混在一起?拆分它们
**大多数场景下,Compose的重组比你想象中快。先写出清晰的逻辑,再考虑优化。**
---
## 六、成为高手的那个瞬间
我曾经带过一位同事,他学Compose三个月,能写、能改、能上线。但总觉得哪里别扭。
有一天他在改一个订单详情页,页面有七八个状态:订单信息、物流轨迹、倒计时、售后入口……代码里散落着七八个`mutableStateOf`,逻辑交织,改一个怕动三个。
我问他:“你觉得这个页面本质上在描述什么?”
他想了很久:“一个订单从生成到完成的不同样子。”
“对。所以你不需要管理七个状态,你只需要管理一个——**订单状态**。其他UI分支,都是根据它计算出来的。”
他重构了代码,删掉五个状态变量,只保留了`orderState`,UI逻辑瞬间清晰。
那天他跟我说:“我终于知道‘状态驱动UI’是什么意思了——**我不指挥UI做什么,我只告诉它,数据现在是哪一页。**”
那一刻,我知道他成了真正的高手。
---
## 写在最后
Compose不难学,一周就能上手写页面。但要把代码写得清晰、稳定、易维护,需要的是对“状态”的深刻理解。
状态是数据,UI是投影。
你不操纵投影,你调整光源。
光源变了,投影自然随之而动。
**初学者关心“界面长什么样”,高手关心“状态是什么结构”。**
**初学者在写界面,高手在设计状态的演化路径。**
如果你现在回头看看自己写的Compose代码,
那个让UI更新的“源头”,
是一个清晰定义、单一来源、可预测变化的状态模型,
还是散落在各个Composable里的零星变量?
想清楚这个问题,你就离真正的Compose高手,不远了。
---
**互动:**
你在Compose状态管理上踩过最大的坑是什么?
评论区聊聊,点赞最高的三位,送你一份我整理的《Compose状态管理实战案例集》。
本站不存储任何实质资源,该帖为网盘用户发布的网盘链接介绍帖,本文内所有链接指向的云盘网盘资源,其版权归版权方所有!其实际管理权为帖子发布者所有,本站无法操作相关资源。如您认为本站任何介绍帖侵犯了您的合法版权,请发送邮件
[email protected] 进行投诉,我们将在确认本文链接指向的资源存在侵权后,立即删除相关介绍帖子!
暂无评论