Table of contents
Open Table of contents
一、为什么需要remember
在传统 View 系统里,UI 更新是去修改属性(setText)。
但在 Compose 里,UI 更新(重组)意味着这个函数被重新执行了一遍。 --- UI 即函数
既然是重新执行函数,那函数里面的局部变量会发生什么?
答案是:会被全部重新创建、重新初始化。
这就是 remember 必须要出场的两大核心场景:
场景一:保住组件“状态”不被重置(最常见场景)
假设我们现在在写一个微信的点赞按钮
1.1 错误示范(没有 remember)
@Composable
fun LikeButton() {
// 每次 LikeButton 这个函数被重新执行时,isLiked 都会被重新变成 false!
val isLiked = mutableStateOf(false)
Button(onClick = { isLiked.value = !isLiked.value }) {
Text(if (isLiked.value) "取消赞" else "点赞")
}
}
问题现场:
- 点击了按钮,
onClick触发,把isLiked改成了true。 - Compose 收到状态变更通知,立刻重新执行
LikeButton()函数来刷新 UI。 - 结果刚一进函数第一行,
isLiked又被这句代码初始化成了false,按钮永远都是“点赞”状态。
1.2 正确示范(加上 remember)
@Composable
fun LikeButton() {
// 加上 remember
val isLiked = remember { mutableStateOf(false) }
Button(onClick = { isLiked.value = !isLiked.value }) { ... }
}
这里 remember 的作用:
- 第一次执行函数时,
remember把false这个状态装进“保险箱”存起来。 - 当点击按钮把状态变成
true并触发重组(函数第二次执行)时,代码走到这一行时,remember说:“停!我‘保险箱’里有上次存的值true,不要再执行大括号里的mutableStateOf(false)了,直接把‘保险箱’里的拿去用。” - 于是,点赞状态成功保留了下来。
场景二:避免重复执行“耗时操作”(性能优化场景)
假设界面每次刷新,都要对一个包含 10 万条数据的 List 进行极其复杂的过滤和排序。
2.1 错误示范(没有 remember)
@Composable
fun UserList(users: List<User>) {
// 极其耗时的操作,比如耗时 200 毫秒
val sortedUsers = users.filter { it.isActive }.sortedBy { it.score }
// 假设下面还有动画,导致这个函数每秒钟被重组 60 次
LazyColumn { ... }
}
问题现场: 如果界面有动画或其他状态变化导致不断重组,这个耗时的排序操作每秒会被执行 60 次!手机直接卡死。
2.2 正确示范(使用 remember 缓存计算结果)
@Composable
fun UserList(users: List<User>) {
// 只有第一次进这个函数时,才会执行大括号里的耗时排序
// 后续哪怕疯狂重组,只要 users 这个参数没变,就直接返回上次排好序的列表
val sortedUsers = remember(users) {
users.filter { it.isActive }.sortedBy { it.score }
}
LazyColumn { ... }
}
这里留意加了一个参数 remember(users):这是带 Key 的 remember,它的含义是:
-
只要
users这个源数据不发生改变,每次函数进来就一直用缓存的排序结果; -
如果
users变了,再重新执行大括号里的耗时计算并更新缓存。
二、总结
在 Compose 中,由于状态变更会引发 Composable 函数的频繁重新执行(重组),
如果我们在函数内部直接声明变量或执行耗时计算,每次重组都会导致变量重置或重复计算。
remember的作用就是在重组的生命周期中跨越函数调用来缓存对象。它主要解决两个问题:
一是包裹
State以防止状态在重组时丢失;二是缓存高开销的计算结果,避免在每一帧重组时造成性能浪费。