前几天的文章,整理了上个月的学习心得。这段时间又仔细研究了下SideEffect,秉着闲着找事干,有事干才不想着去洗脚的原则,我又开始了读书笔记式的码字。源码在文章末尾。各副作用函数特性表也在文章末尾。
部分例子在实际开发中是可能存在一定瑕疵的,旨在让大家明白如何使用。使用时请注意
一、副作用到底有哪些?
- LaunchedEffect
- DisposableEffect
- SideEffect
- rememberCoroutineScope
- produceState
副作用函数关联概念(个人觉得不是副函数): - derivedStateOf
- rememberUpdatedState
上篇文章我已经简单介绍了这几个副作用函数,可总觉得太过简略,例子也很简单,完全不贴合实际开发,以后我怎么抄代码嘛。好的,下面我们就从实际出发举例子,让大家更好抄更好理解。
LaunchedEffect
1、概括
LaunchedEffect(key) = 页面进入时自动启动协程,key变了就取消旧的,重启新的。
2、核心机制
| 机制 | 说明 |
|---|---|
| 自动启动 | Composable 进入组合时,自动 launch 协程 |
| key 控制 | key 不变 → 协程继续;key 变 → 取消旧协程,启动新协程 |
| 自动取消 | 离开页面 → 自动 cancel(),零泄漏 |
3、实际开发中的场景
- 场景一:加载网络数据(最常见)
kotlin
@Composable
fun FetchUserInfo(userId: Int) {
var user by remember { mutableStateOf<User?>(null) }
var loading by remember { mutableStateOf(true) }
var error by remember { mutableStateOf<String?>(null) }
LaunchedEffect(userId) { // key = userId
loading = true
error = null
try {
val result = withContext(Dispatchers.IO) {
RetrofitClient.apiService.getUsersById(userId)
}
user = result
} catch (e: Exception) {
error = e.message
} finally {
loading = false
}
}
when {
loading -> CircularProgressIndicator()
error != null -> Text("错误: $error", color = Color.Red)
user != null -> Column {
Text("用户名: ${user!!.name}", style = MaterialTheme.typography.bodyLarge)
Text("邮箱: ${user!!.email}")
}
}
}
代码核心:切换 userId → 自动取消旧请求,发起新请求
- 场景二:一次性任务初始化(日志、埋点、第三方初始化)
kotlin
@Composable
fun AnalyticsScreen(screenName: String) {
LaunchedEffect(Unit) { // 只执行一次
//Analytics.uploadPoint(screenName)
Log.i("当前页面: $screenName")
}
Text("当前页面: $screenName")
}
为什么使用Unit?Unit在Kotlin里面是常量,意味着key永远不变 ,所以只在页面首次进入时执行一次。
小知识:LaunchedEffect(true)和LaunchedEffect(Unit)行为一致,因为 true 也是常量。但 true容易让人误解为"每次都执行",建议统一用 Unit。
- 场景三:轮询(实时获取数据)
kotlin
@Composable
fun GoldPrice(symbol: String) {
var price by remember { mutableStateOf<Price?>(null) }
LaunchedEffect(symbol) {
while (true) {
price = withContext(Dispatchers.IO) {
GoldPriceRetrofitClient.apiService.getGoldPrice(symbol)
}
delay(30_000)
}
}
Card(
modifier = Modifier.padding(16.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 4.dp)
) {
Column(modifier = Modifier.padding(16.dp)) {
Text(symbol, style = MaterialTheme.typography.bodyMedium)
Text("¥${price?.price?:"未知"}", style = MaterialTheme.typography.bodyLarge)
}
}
}
代码核心:每30秒获取一次金价。调整商品编码时,自动停止旧商品轮询,开始新轮询。
- 场景四:执行一个协程
kotlin
@Composable
fun LoadData(viewModel: SideEffectsViewModel = viewModel()) {
var user by remember { mutableStateOf<User?>( null) }
LaunchedEffect(Unit) {
user = viewModel.getUserInfo(1)
}
}
代码核心:执行获取用户查询的supend的方法
4、快速记忆
进入页 → 自动启动 → key变化自动重启 → 离开自动取消
5、常犯错误
| 错误 | 后果 | 正确 |
|---|---|---|
LaunchedEffect(true) |
易产生语义歧义 | 用 Unit |
不用 withContext(IO) |
阻塞 UI | 必须切线程 |
忘记 key |
数据不更新 | 必须加依赖参数 |
6、总结
LaunchedEffect 是Compose中页面级异步任务的首选,key+自动取消是其核心。
DisposableEffect
1、概括
DisposableEffect(key) = 「进入时注册,离开时自动清理」的资源管理神器
2、核心机制
| 机制 | 说明 |
|---|---|
| 进入时执行 | Composable 进入组合时,执行 effect 块 |
| key 控制 | key 变化 → 先 onDispose,再重新注册 |
| 离开时自动清理 | 离开页面 → 自动调用 onDispose,防止内存泄漏 |
3、实际开发中场景
- 场景一:注册/注销广播接收器(最常见)
kotlin
@Composable
fun NetworkChangeListener(context: Context) {
val connectivityManager = remember {
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
}
var isConnected by remember { mutableStateOf(true) }
DisposableEffect(Unit) {
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
isConnected = true
}
override fun onLost(network: Network) {
isConnected = false
}
}
connectivityManager.registerDefaultNetworkCallback(callback)
onDispose {
connectivityManager.unregisterNetworkCallback(callback) // 必须清理!
}
}
Card(
colors = CardDefaults.cardColors(
containerColor = if (isConnected) Color.Green else Color.Red
)
) {
Text(
text = if (isConnected) "在线" else "离线",
color = Color.White,
modifier = Modifier.padding(8.dp)
)
}
}
代码核心:监听设备网络状态,断开网络显示离线,否则显示在线。
- 场景二:添加移除传感器
kotlin
@Composable
fun StepCounter(sensorManager: SensorManager) {
var steps by remember { mutableStateOf(0) }
DisposableEffect(Unit) {
val sensor = sensorManager.getDefaultSensor(Sensor.TYPE_STEP_COUNTER)
val listener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent) {
steps = event.values[0].toInt()
}
override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {}
}
sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_UI)
onDispose {
sensorManager.unregisterListener(listener)
}
}
Text("今日步数: $steps 步", style = MaterialTheme.typography.headlineMedium)
}
代码核心:实时监测运动传感器,统计步数。
- 场景三:订阅取消Flow(ViewModel 搭配使用)
kotlin
@Composable
fun FlowSubscription(viewModel: SideEffectsViewModel = viewModel()) {
var message by remember { mutableStateOf("等待消息...") }
DisposableEffect(Unit) {
val job = viewModel.messageFlow
.onEach { message = it }
.launchIn(viewModel.viewModelScope)
onDispose {
job.cancel() // 取消订阅
}
}
Column(modifier = Modifier.fillMaxSize()) {
Button(onClick = {
viewModel.sendMessage()
}, modifier = Modifier.padding(16.dp)) {
Text("点击发消息")
}
Text(message)
}
}
代码核心:离开时,自动取消Flow
- 场景四:key变化时,重置资源(如切换用户)
kotlin
@Composable
fun UserSessionTracker(userId: String) {
DisposableEffect(userId) {
LoginManager.login(userId)
println("用户 $userId 已登录")
onDispose {
LoginManager.logout()
println("用户已登出")
}
}
Text("当前用户: $userId")
}
代码核心:切换 userId → 自动登出旧用户,登录新用户
4、快速记忆
进入注册 → key变化重置 → 离开自动清理 → 安全无泄漏
5、常见错误
| 错误 | 后果 | 正确 |
|---|---|---|
忘记 onDispose |
内存泄漏 | 必须清理 |
用 LaunchedEffect 注册监听 |
无法手动清理 | 用 DisposableEffect |
key 写错 |
重复注册 | 用稳定 key |
6、总结
DisposableEffect 是 Compose 中「资源生命周期管理」的唯一正确方式, 掌握 onDispose + key,解决内存泄漏的问题
SideEffect
1、一句话概括
SideEffect = 「每次重组都执行一次」的轻量副作用,不自动取消,适合日志、埋点、同步非 Compose 状态
2、核心机制
| 机制 | 说明 |
|---|---|
| 每次重组都执行 | 只要 UI 重组,SideEffect 就运行一次 |
| 无 key 控制 | 不支持 key,不能重启或取消 |
| 不自动清理 | 离开页面不会自动取消,需手动控制 |
3、实际开发中的四大场景
- 场景一:页面曝光埋点(常见)
kotlin
@Composable
fun AnalyticsScreen(screenName: String) {
SideEffect {
Analytics.logScreenView(screenName) // 每次重组都上报
Log.i("compose 曝光","曝光: $screenName")
}
Text("当前页面: $screenName")
}
- 场景二:日志输出及调试
kotlin
@Composable
fun DebugRecomposition() {
var count by remember { mutableStateOf(0) }
SideEffect {
Log.d("compose日志","重组发生了!count = $count")
}
Column {
Text("Count: $count")
Button(onClick = { count++ }) { Text("加1") }
}
}
- 场景三:同步或发送数据到外部(如SharedPreferences、EventBus、ViewModel)
kotlin
@Composable
fun SyncTheme(isDark: Boolean) {
val prefs = LocalContext.current.getSharedPreferences("settings", Context.MODE_PRIVATE)
SideEffect {
prefs.edit().putBoolean("dark_mode", isDark).apply()
Log.d("compose 日志", "主题已保存: ${if (isDark) "深色" else "浅色"}")
EventBus.post(isDark)
}
// UI...
}
kotlin
@Composable
fun BrightnessControl(viewModel: SideEffectsViewModel = viewModel()) {
var brightness by remember { mutableFloatStateOf(0.5f) }
// 每次 brightness 改变并重组完成后,同步到 ViewModel
SideEffect {
viewModel.currentBrightness = brightness
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("当前亮度: ${(brightness * 100).toInt()}%")
Slider(value = brightness, onValueChange = { brightness = it })
}
}
核心:每次切换主题 → 自动保存到本地,并通知其他页面
- 场景四:与非 Compose 组件通信(如 TextureView)
kotlin
@Composable
fun CameraPreview(textureView: TextureView) {
SideEffect {
CameraManager.bind(textureView.surfaceTexture)
}
AndroidView(factory = { textureView })
}
4、快速记忆
重组就跑,轻量选它,日志埋点,首选SideEffect。
5、常见错误
| 错误 | 后果 | 正确 |
|---|---|---|
| 放网络请求 | 每次重组都请求 | 用 LaunchedEffect |
| 放耗时操作(非轻量) | 卡 UI | 用 withContext(IO) + LaunchedEffect |
| 期望只执行一次 | 不会 | 用 LaunchedEffect(Unit) |
总结:每次重组都会执行。注意:SideEffect 是在 重组完成后立即执行 ,没有协程。而 LaunchedEffect 是在进入组合时启动协程。
rememberCoroutineScope
1、一句话概括:
rememberCoroutineScope() = 「手动、可控、生命周期安全 」的协程作用域,重组不重启,离开页面自动取消。
2、核心机制
| 机制 | 说明 |
|---|---|
| 手动启动 | 必须 scope.launch { } 才执行 |
| 重组不影响 | 重组时 同一个 scope,不会重新创建 |
| 自动取消 | 离开页面 → 自动 cancel() ,所有协程停止 |
3、常用场景
- 场景一:异步操作(如文件保存,最常见)
kotlin
@Composable
fun SaveButton(viewModel: SaveViewModel) {
val scope = rememberCoroutineScope()
var saving by remember { mutableStateOf(false) }
Button(
enabled = !saving,
onClick = {
saving = true
scope.launch {
try {
viewModel.saveData() // 模拟文件保存
} finally {
saving = false // 发送错误时,重置UI
}
}
}
) {
Text(if (saving) "保存中..." else "保存")
}
}
场景二:定时任务
kotlin
@Composable
fun TimerWithCoroutineScope() {
val scope = rememberCoroutineScope()
var count by remember { mutableIntStateOf(0) }
Button(onClick = {
scope.launch {
repeat(10) {
delay(1000)
count++
}
}
}) {
Text("开始定时任务")
}
Text("计数: $count")
}
场景三:批量处理
kotlin
@Composable
fun BatchProcess(items: List<String>) {
val scope = rememberCoroutineScope()
var progress by remember { mutableStateOf(0) }
Button(onClick = {
scope.launch {
items.forEachIndexed { index, item ->
processItem(item)
progress = index + 1
}
}
}) {
Text("处理 ${items.size} 项")
}
LinearProgressIndicator(progress = progress / items.size.toFloat())
}
- 场景四:与Snackbar搭配(Material3 常见用法)
kotlin
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SnackbarExample() {
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
Scaffold(
snackbarHost = { SnackbarHost(snackbarHostState) },
floatingActionButton = {
FloatingActionButton(onClick = {
scope.launch {
snackbarHostState.showSnackbar("Hello Snackbar!")
}
}) {
Text("点我")
}
}
) { padding ->
Box(Modifier.fillMaxSize().padding(padding), contentAlignment = Alignment.Center) {
Text("点击右下角按钮显示 Snackbar")
}
}
}
4、快速记忆
rememberCoroutineScope → 手动 launch → 自动取消 → 重组不重启
它与 LaunchedEffect 最大的区别是:需要事件触发 而不是自动执行。
5、常见错误
| 错误 | 后果 | 正确 |
|---|---|---|
用 LaunchedEffect 放点击 |
每次重组都弹 | 用 scope.launch |
不用 remember |
每次重组新 scope | 必须 rememberCoroutineScope() |
用 GlobalScope |
无法取消 | 禁止使用! |
6、总结
rememberCoroutineScope 是 Compose 中「用户交互 → 异步操作」的最佳实践,需要手动启动协程,首选选他。
ProduceState
1、一句话概括
produceState = 「外部数据源 转化为 Compose State」的桥梁,自动协程 + 自动取消 + 支持 key
2、核心机制
| 机制 | 说明 |
|---|---|
| 协程生产 State | 在协程中 value = xxx → 自动触发 UI 重组 |
| 自动生命周期 | 进入 → 启动协程;离开 → 自动取消 |
| key 控制重启 | key 变 → 取消旧协程,启动新协程 |
| 其更像是collectAsState()的增强版本,支持任意数据源,而collectAsState则只能来源于Flow。 |
常用场景
- 场景一:请求网络数据(最常见)
kotlin
@Composable
fun UserInfo(userId: Int) {
val user by produceState<User?>(initialValue = null, userId) {
value = withContext(Dispatchers.IO) {
RetrofitClient.apiService.getUsersById(userId) //根据userId获取用户数据
}
}
when (user) {
null -> CircularProgressIndicator()
else -> Text("欢迎,${user!!.name}")
}
}
- 场景二:Flow -> State(较常见),替代collectAsState
kotlin
class SideEffectsViewModel : ViewModel() {
private val _count = MutableStateFlow(0)
val countFlow = _count.asStateFlow()
fun increment() = _count.value++
}
@Composable
fun FlowCounter(viewModel: SideEffectsViewModel = viewModel()) {
//原写法
// val count by viewModel.collectAsState()
val count by produceState(initialValue = 0) {
viewModel.countFlow.collect { value = it }
}
Text("Count: $count")
}
- 场景三:回调函数-> State
kotlin
@Composable
fun LocationNow(context: Context, locationManager: LocationManager) {
val location by produceState<String?>(initialValue = null) {
val listener = LocationListener { location -> value = "经度: ${location.longitude}, 纬度: ${location.latitude}" }
//权限检查代码省略
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
2000L,
1f,
listener)
awaitDispose {
locationManager.removeUpdates(listener)
}
}
if (location == null) {
Text("正在获取位置...")
} else {
Text("location")
}
}
- 场景四:状态处理
kotlin
sealed class Result<out T> {
object Loading : Result<Nothing>()
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
}
@Composable
fun <T> loadData(
key: Any?,
block: suspend () -> T
): State<Result<T>> = produceState<Result<T>>(Result.Loading, key) {
try {
value = Result.Success(block())
} catch (e: Exception) {
value = Result.Error(e.message ?: "未知错误")
}
}
4、快速记忆
生产 State → 协程运行 → 自动取消 → key 控制重启
5、常见错误
| 错误 | 后果 | 正确 |
|---|---|---|
不用 withContext(IO) |
阻塞 UI | 必须切线程 |
忘记 key |
数据不更新 | 加依赖参数 |
返回 State |
编译错误 | 必须 by produceState |
6、相似对比
| 项目 | produceState |
collectAsState() |
|---|---|---|
| 数据源 | 任意 | 仅 Flow |
| 支持 key | Yes | No |
| 自动取消 | Yes | Yes |
7.总结:
produceState 是 Compose 中【外部数据源 -> State】的最好桥梁, 熟悉它,你就能实现任意数据源驱动UI变化了。
derivedStateOf
有人把这个归于副作用函数之类,但我个人觉得不是。原因如下:
- 纯函数计算:基于其他状态计算的派送状态,不产生任何外部影响
- 无外部交互:不执行I/O操作,不启动异步任务、不修改外部状态
- 不直接参与UI更新:虽然在 Composable 中触发,但只是影响 UI 的"外部世界"
1、一句话概括:
derivedStateOf { } = 「只有当结果真正变化时才触发重组」,避免重复计算 + 减少 UI 闪烁
2、为什么需要derivedStateOf?
问题:频繁重组导致性能拉胯
kotlin
@Composable
fun UserFilter(nameItems: List<String>, query: String) {
val filtered = nameItems.filter { it.contains(query) } // 每次重组都过滤!
LazyColumn {
nameItems(filtered) { Text(it) }
}
}
这段代码你会发现query 变 → 重组 → 每次都重新 filter 。 即使结果没变,也会触发 LazyColumn 重绘 → 性能差 + 闪烁 。
相关改进参考场景一
3、常用场景
- 场景一:搜索过滤
kotlin
@Composable
fun SearchStr(nameItems: List<String>, query: String) {
// 使用 derivedStateOf 只在 query 或 nameItems 变化时重新计算
var searchQuery by remember { mutableStateOf(query) }
val filteredItems by derivedStateOf {
nameItems.filter { it.contains(searchQuery, ignoreCase = true) }
}
Column {
TextField(value = searchQuery,
onValueChange = { searchQuery = it },
label = { Text("搜索") })
LazyColumn {
items(filteredItems) { item ->
Text(item)
}
}
}
}
代码核心:快速输入时 → 只有结果变了才更新列表,降低了重组次数
- 场景二:分页加载判断
kotlin
@Composable
fun InfiniteList(items: List<String>, onLoadMore: () -> Unit) {
val listState = rememberLazyListState()
// 只有到达底部时才触发
val shouldLoadMore by derivedStateOf {
val lastVisible = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
lastVisible >= items.size - 2
}
LaunchedEffect(shouldLoadMore) {
if (shouldLoadMore) onLoadMore()
}
LazyColumn(state = listState) {
items(items) { Text(it) }
}
}
代码核心:计算是否距离底部还有两个Item,是的话触发加载下一页回调操作。
- 场景三:关联数据或状态实时计算(常见用途)
kotlin
@Composable
fun SumAndAvg(numbers: List<Int>) {
val sum by remember {
derivedStateOf {
numbers.sum()
}
}
val avg by remember {
derivedStateOf {
numbers.average()
}
}
Card {
Column(modifier = Modifier.padding(16.dp)) {
Text("总和: $sum")
Text("平均: %.2f".format(avg))
Text("Numbers 总数: ${numbers.size}")
}
}
}
-场景四:控件显示状态判断 (常见)
kotlin
@Composable
fun TopButtonShow(listState: LazyListState) {
val scope = rememberCoroutineScope()
val showButton by remember {
derivedStateOf {
listState.firstVisibleItemIndex > 0
}
}
AnimatedVisibility(showButton) {
FloatingActionButton(onClick = {
// 回到顶部
scope.launch {
listState.animateScrollToItem(0)
}
}, modifier = Modifier.padding(16.dp)) {
Icon(Icons.Default.KeyboardArrowUp, contentDescription = "回到顶部")
}
}
}
4、快速记忆
数据派生 -> 重组少 -> 性能优 -> 体验棒
5、常见错误
| 错误 | 后果 | 正确 |
|---|---|---|
不用 derivedStateOf |
频繁重组 | 必须用 |
放 LaunchedEffect |
无法驱动 UI | 用 by derivedStateOf |
| 计算太重 | 卡 UI | 移到 IO 线程 |
6、总结
derivedStateOf 是 Compose 中「计算优化 + 防闪烁」的最优解之一,掌握它能让你的UI更流畅。
rememberUpdatedState
我同样认为这不属于副作用函数,理由与derivedStateOf类似。但它与副作用LaunchedEffect函数配合紧密,所以一起介绍了。其实这里面涉及到一个闭包的概念。这里面就不展开讲,不了解的小伙伴去问AI吧。
1、一句话概括
rememberUpdatedState(value) = 「让 LaunchedEffect 里的协程,始终拿到最新的值」,即使它在重组时被捕获了旧值。
2、解决了什么问题
先看问题代码:
kotlin
@Composable
fun GreetingMessage(message: String) {
// 启动一个协程,每隔2秒打印一次 message
LaunchedEffect(Unit) {
while (true) {
delay(2000)
Log.d("message的值","当前 message: $message") // 闭包捕获旧值
}
}
}
存在的问题:当 message 改变时,LaunchedEffect(Unit) 不会重启,因为key未发生变化; 因此协程里打印的仍然是"第一次 Composition"时的旧值; 这就叫「stale capture(过期捕获) 」。
解决方案:使用rememberUpdatedState进行修正
kotlin
@Composable
fun UpdateNewMessage(message: String) {
// 让协程中总能访问最新 message
val currentMessage by rememberUpdatedState(message)
LaunchedEffect(Unit) {
while (true) {
delay(2000)
println("当前 message: $currentMessage") // ✅ 始终是最新值
}
}
}
3、应用场景
- 场景一:防抖(推荐此方式实现Compose的防抖)
kotlin
@Composable
fun DebouncedSearch(
query: String,
onDebouncedQueryChange: (String) -> Unit
) {
var searchQuery by remember { mutableStateOf(query) }
val currentOnDebouncedQueryChange by rememberUpdatedState(onDebouncedQueryChange)
LaunchedEffect(searchQuery) {
delay(300) // 300ms 防抖延迟
if(searchQuery.isNotBlank()) {
currentOnDebouncedQueryChange(query)
}
}
TextField( value = searchQuery,
onValueChange = { searchQuery = it },
label = { Text("搜索") } )
}
- 场景二:事件监听中保持回调更新(最常见)
kotlin
@Composable
fun LocationChange(onLocationUpdate: (Location) -> Unit) {
val currentCallback by rememberUpdatedState(onLocationUpdate)
DisposableEffect(Unit) {
val listener = LocationListener { location ->
currentCallback(location) // 使用最新的回调
}
locationManager.requestLocationUpdates(listener)
onDispose {
locationManager.removeUpdates(listener)
}
}
}
代码核心:时刻保持最新的回调,解决持有旧回调的问题
- 场景三:定时器动态轮询(根据情况调整轮询时间)
kotlin
/**
* 每次调用调整轮询间隔,不触发重启
*/
@Composable
fun PollingData(intervalMs: Long) {
val updatedInterval by rememberUpdatedState(intervalMs)
var data by remember { mutableStateOf("加载中...") }
LaunchedEffect(Unit) {
while (true) {
data = withContext(Dispatchers.IO) {
RetrofitClient.apiService.getUsersById(1).toString()
}
delay(updatedInterval) // 动态间隔
}
}
Column {
Text("数据: $data")
Text("轮询间隔: $updatedInterval ms")
}
}
- 场景四:延迟提示(优化部分情况下,控件多次显示隐藏导致的闪烁)
kotlin
@Composable
fun DelayedTooltip(visible: Boolean, text: String) {
val updatedVisible by rememberUpdatedState(visible)
LaunchedEffect(Unit) {
delay(1000) // 延迟显示
if (updatedVisible) {
showTooltip(text)
}
}
// UI...
}
4、快速记忆
协程不重启,值要最新值,那就使用UpdatedState
LaunchedEffect(Unit) + rememberUpdatedState(value) = 不重启协程也能拿到最新值
5、常犯错误
| 错误 | 后果 | 正确 |
|---|---|---|
不用 rememberUpdatedState |
协程用旧值 | 必须用 |
配合 LaunchedEffect(value) |
频繁重启 | 用 Unit |
放 SideEffect |
每次重组都执行 | 错! |
六、总结
rememberUpdatedState 是 Compose 中「协程 + 动态参数」的黄金搭档,经常与LaunchedEffect(Unit)搭配实现多场景功能。
总结:
Compose 的副作用函数本质是:在声明式UI中执行命令式逻辑的入口 。
LaunchedEffect、DisposableEffect、SideEffect 解决不同生命周期阶段的副作用;
而 rememberCoroutineScope、produceState、derivedStateOf、rememberUpdatedState 则是围绕状态、协程与性能优化的工具。理解它们的生命周期、触发时机与取消机制,是写好 Compose 的关键。
本文只列出了我能想到的一些场景,肯定有很多的遗漏,还是需要大家在使用中去完善了解
各副作用函数机制速查表:
| 函数 | 启动时机 | 重组触发 | 自动取消(随生命周期销毁) | 常见用途 | 是否可持久运行(如轮询) | 是否依赖 key |
|---|---|---|---|---|---|---|
SideEffect |
每次成功重组后执行 | ✅ 每次重组都会执行 | ❌ 不会自动取消 | 把 Compose 状态同步到非 Compose 系统(如 Log、ViewModel 状态等) | ❌ | 否 |
LaunchedEffect(key) |
进入 Composition 时执行一次 | ✅ key 变化会重启协程 | ✅ key 改变或退出 Composition 时自动取消 | 执行一次性任务、启动协程、监听 Flow | ✅ | 是 |
DisposableEffect(key) |
进入 Composition 时执行一次 | ✅ key 变化或离开 Composition 时触发 onDispose | ✅ 自动触发 onDispose | 注册/注销监听、启动/停止外部资源 | ✅ | 是 |
rememberCoroutineScope() |
组合时创建一个可记忆的协程作用域 | ❌ 不会因重组重新创建 | ✅ Composition 离开时自动取消 | 需要在回调中手动启动协程(如按钮点击) | ✅ | 否 |
produceState() |
进入 Composition 时启动协程 | ✅ key 变化会重启生产逻辑 | ✅ 自动取消旧协程 | 把异步任务结果转为 State(如网络请求) | ✅ | 是 |
derivedStateOf() |
依赖 State 变化时计算 | ✅ 依赖的值变化才重新计算 | ❌(纯计算,此处无意义) | 优化计算型状态,避免不必要重组 | ❌ | 否 |
rememberUpdatedState() |
每次重组更新内部值 | ❌ 不会导致重组 | ❌ | 让长期副作用中访问最新参数(如协程/回调) | ✅ | 否 |
snapshotFlow { } |
在协程中把 State 转换为 Flow | ✅ State 变化时发射新值 | ✅ 协程取消即停止 | 响应式收集 State 变化 | ✅ | 否 |