- 不要在 Composable 函数体中直接修改状态
🚫 错误示例:
@Composable
fun MyScreen(viewModel: MainViewModel) {
viewModel.updateSomething() // ❌ 每次重组都会调用
}
正确写法:
@Composable
fun MyScreen(viewModel: MainViewModel) {
LaunchedEffect(Unit) {
viewModel.updateSomething()
}
} - 不要在状态更新函数中同时读取并写入状态(除非你确定该状态在 UI 未被观察)
错误示例(易造成死循环):
val logContent = mutableStateOf("")
fun addLog(msg: String) {
logContent.value = logContent.value + "\n" + msg // ❌ 读 + 写
}
如果 UI 使用了 logContent.value,这会在写入时触发重组,反复进入这个函数。
✅ 推荐替代:
改用 List + append;
或用 StateFlow 替代;
或加锁/节流/惰性更新
- 在 Composable 中观察状态:用 collectAsState() 或 remember + derivedStateOf
示例(使用 Flow 安全绑定 UI):
val logs by viewModel.logFlow.collectAsState()
Text(text = logs)
示例(局部记忆派生状态):
val firstLine by remember(logs) {
derivedStateOf { logs.lines().firstOrNull() ?: "" }
} - 在可组合函数中使用 ViewModel 的方法,只能在以下场景中
初始化一次 LaunchedEffect(Unit) { viewModel.initSomething() }
每次参数变化执行 LaunchedEffect(id) { viewModel.fetchData(id) }
UI事件触发 Button onClick 中调用即可 ✅ - 不使用的状态,不要持有 mutableStateOf
即使你不在 UI 使用该值,写入 mutableStateOf 也会在 snapshot 系统中注册变化,可能触发隐藏的重组链。
✅ 推荐:
改用普通变量(var)或 mutableList;
或使用 StateFlow + 明确 collectAsState 的组合方式。
- Compose 状态更新应避免链式依赖(A 更新 → B 观察 A → 写回 A)
🚫 错误链条(易死循环):
mutableStateA.value = ...
mutableStateB.value = mutableStateA.value
mutableStateA.value = mutableStateB.value // 🔁
对状态进行回写要加 guard 或条件判断。
7.小结:Compose 状态安全三原则
原则 描述
🧼 幂等组合 Composable 只表达 UI,不做逻辑
🎯 控制副作用 所有副作用都必须进入 LaunchedEffect 等容器
🔁 状态更新稳定 状态读写不要在 UI 和逻辑中交叉调用、混用 Snapshot + Flow