Compose 基础 - 附带效应(副作用)Effects

发布时间:2026/11/4 3:20:07

Compose 基础 - 附带效应(副作用)Effects 一、概念Compose中的附带效应指可组合函数作用域内引起外部的状态变化可组合函数是用来声明界面的不相关的操作都是副作用Side Effect。可组合函数的特点重组可能舍弃中途打断代码再重新执行、并行执行调用和被调用的可组合项在不同线程、可能执行非常频繁耗时操作卡顿。组合需要处理的副作用执行时机要明确例如挂载时、重组时。执行次数要可控是否应该随着重组反复执行。不会造成泄漏移除时释放资源。1.1 纯函数函数与外界交换数据只能通过形参和返回值进行不会对外界环境造成影响。此外纯函数是幂等的唯一输入参数决定唯一输出返回值不会因为运行次数的增加导致返回值的不同。这对于声明式UI框架至关重要因为它们都是通过函数的反复执行来渲染UI的函数执行的时机和次数都不可控但是函数的执行结果必须可控因此我们要求这些函数组件必须用纯函数实现。1.2 副作用函数内部与外界进行了交互产生了其它结果如修改外部变量。虽然我们不希望函数执行中出现副作用但现实情况是有一些逻辑只能作为副作用来处理。例如一些IO操作、计时、日志埋点等这些都是会对外界或收到外界影响的逻辑不能无限制的反复执行所以需要能够合理地处理一些副作用。副作用的执行时机是明确的例如在Recomposition时。副作用的执行次数是可控的不应该随着函数反复执行。副作用不会造成泄露例如对于注册要提供适当的时机进行反注册1.3 可组合函数的生命周期由于没有暴露对应的生命周期回调方法可以在组合项中使用不同的附带效应来完成相似的作用。onActiveEnter首次挂载到组件树上显示。可以使用 LaunchedEffect 挂载时就执行。onCommitComposition重组刷新UI执行0/N次。可以使用 SideEffect 每次重组时都执行。onDisposeLeave从组件树上移除不再显示。可以使用 DisposableEffect 移除时执行回调。二、Effect API也是可组合函数该函数不会发出界面并且在组合完成后不会产生附带效应。自适应界面本质上是异步的副作用往往也都是耗时操作动画也是因此引入协程而非回调来解决问题。重启效应那些带有 key 的效应 key是个变长参数能传入多个 key 控制 block 的重新执行之前block中的代码未执行完会先取消协程然后再次执行key 不变时发生重组不会重新执行 block 由于常量不变可以使用Unit、true当作key这样就遵循了调用点的生命周期。业务LaunchedEffect组件首次挂载时执行key不变的话不受重组影响key变化先取消协程再重新执行可组合项被移除时自动取消协程。作用域中提供了协程环境。适用于耗时操作。SideEffect执行在 LaunchedEffect 和 DisposableEffect 之前。key不变的话不受重组影响。无key版本在每次成功完成重组后都执行适用于将状态同步共享给非Compose管理的对象适合触发依赖于用户界面最终稳定状态的副作用。DisposableEffect组件首次挂载时执行key不变的话不受重组影响key变化或可组合项被移除时会回调 onDispose() 释放资源。适用于注册取消监听、获取释放资源。rememberCoroutineScope提供协程作用域对象remrember返回的该对象不受重组影响开启的子协程也不会受重组影响可组合函数被移除时自动取消协程。解决了像 onClick 这种不是组合函数无重组作用域的地方无法调用上面三个API开启协程的情况可在不同子元素中启动协程来统一管理多个子协程常用于动画。状态rememberUpdatedState让不受重组影响的附带效应如 LaunchedEffect 在作用域中捕获的外部状态是最新的值。由于 LaunchedEffect 不受重组影响执行 delay 的三秒期间捕获的外部参数状态发生多次变化delay结束后老的参数已经不存在了无法获取新的参数也无法得到获取。使用 rememberUpdatedState 包裹一下该状态LaunchedEffect 就能在 delay 三秒后读取到该状态的最新值。produceState基于现有API创建自己的效应移除时会自动取消协程。用于将回调式数据源、非协程的异步数据源、非Compose可观察的数据源Flow、LiveData、RxJava转为Compose可观察的状态。derivedStateOf合并多个状态减少重组。一个状态基于另一个或多个状态得出即对条件状态经过计算后得出结果状态。对多个状态进行提取避免每次条件状态更新都要连带自己重组。通常使用remember的key可以实现有些情况的状态无法用作key例如元素改变了而List没变。snapshotFlow状态转为Flow状态值变化就发送到Flow前后两次状态值相同不发送。2.1 LaunchedEffect()Composablefun LaunchedEffect(key1: Any?,block: suspend CoroutineScope.() - Unit) {val applyContext currentComposer.applyCoroutineContextremember(key1) { LaunchedEffectImpl(applyContext, block) }}内部使用 remember() 来包裹 block如果 key 不发生变化传入的 block 是不会重新被执行因此不受重组影响也因此 block 内部从开始执行到执行到读取的状态的代码这期间如果状态有变化依然还是最初那个值虽然可以通过 key 变化重新执行 block 来获取最新状态但是内部其它的代码也重新执行了使用 rememberUpdatedState() 解决。Composable fun Show() { val viewModel viewModelMainViewModel() val state remember { mutableStateOf(false) } LaunchedEffect(state) { viewModel.loadData() } val data viewModel.dataState }//传入Unit在外部用if判断状态来控制 LaunchedEffect 挂载和移除 //就不会用状态当作key每次改变都执行block未执行完还能取消 Composable fun Show() { val state remember { mutableStateOf(false) } if (state) { LaunchedEffect(Unit) { viewModel.loadData() } } }Composable fun Demo() { var count by remember { mutableStateOf(0) } Column { Button(onClick { count }) { Text(text 当前值$count) } DelayText($count) } } Composable fun DelayText(str: String) { var text by remember { mutableStateOf() } LaunchedEffect(key1 Unit) { delay(3000L) text str } Text(text 延迟后的值$text) }2.2 DisposableEffect()Composablefun DisposableEffect(key1: Any?,effect: DisposableEffectScope.() - DisposableEffectResult)effect即函数作用域中必须调用 onDespose() 函数。inline fun onDispose( crossinline onDisposeEffect: () - Unit ): DisposableEffectResultDisposableEffect(isIntercept) { if (isIntercept){ backDispatcher.addCallback(backCallback) } onDispose { //作用域中必须调用onDespose() backCallback.remove() } }对于生命周期更方便的监听详见Composable fun Demo() { val lifecycleOwner LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { //创建观察者 val observer LifecycleEventObserver { _, event - when (event) { Lifecycle.Event.ON_CREATE - {} Lifecycle.Event.ON_START - {} Lifecycle.Event.ON_RESUME - {} Lifecycle.Event.ON_PAUSE - {} Lifecycle.Event.ON_STOP - {} Lifecycle.Event.ON_DESTROY - {} else - {} } } //把观察者加到生命周期里 lifecycleOwner.lifecycle.addObserver(observer) //可组合项被移除时移除观察者 onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } }2.3 SideEffect()Composablepublic fun SideEffect(key1: Any?, effect: () - Unit)有 key 版本。Composablefun SideEffect(effect: () - Unit)无 key 版本相当于简化版的DisposableEffect无key控制无onDespose回调。//返回值非Unit类型的组合函数名称采用常规的小写开头 Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics remember {...} //不会随重组改变 SideEffect { //将重组后的user新值传递给非Compose管理的对象analytics analytics.setUserProperty(userType, user.userType) } return analytics }2.4 rememberCoroutineScope()Composableinline fun rememberCoroutineScope(crossinline getContext: DisallowComposableCalls () - CoroutineContext { EmptyCoroutineContext }): CoroutineScopeComposable fun MyComposable() { val scope rememberCoroutineScope() var str by remember { mutableStateOf(默认文字) } Button(onClick { scope.launch { str 更改后文字 } }) { Text(点击更改) } Text(str) }2.5 rememberUpdatedState()Composablefun T rememberUpdatedState(newValue: T): StateT remember {mutableStateOf(newValue)}.apply { value newValue }//创建和更新状态集于同一个函数中完成。//延迟3s后执行传入的onTimeOut等待的这3s期间更改了传参onTimeout引发重组 //使用 rememberUpdatedState() 就可以让不受重组影响的LaunchedEffect拿到更新后的onTimeout Composable fun LandingScreen(onTimeout: () - Unit) { val currentOnTimeout by rememberUpdatedState(onTimeout) LaunchedEffect(Unit) { delay(3000) currentOnTimeout() } }//按钮在倒计时5秒内点击会改变最终显示的值但不会打断倒计时 Composable fun One() { var numState by remember { mutableStateOf(0) } Column { Button(onClick { numState 1 }) { Text(text 点击改变值$numState) //按钮文字即时显示是否变化重组 } Two(num numState) } } Composable fun Two(num: Int) { val numState by rememberUpdatedState(newValue num) LaunchedEffect(Unit) { //传入Unit当key不会随重组而重新执行 repeat(5){ delay(1000) Log.e(----------------,倒计时${ 5 - it }) //倒数54321 } Log.e(----------------,最终值$numState) //按钮累计点击次数 } }//监听返回键点击按钮会改变打印内容重组 //但LaunchedEffect不会重新执行却跟随改变了打印内容 Composable fun One(backDispatcher: OnBackPressedDispatcher) { val printA: () - Unit { Log.e(---------------, print A) } val printB: () - Unit { Log.e(---------------, print B) } var printState by remember { mutableStateOf(printA) } Button(onClick { printState if (printState printA) printB else printA }) { Text(text 点击改变打印内容) } Two(backDispatcher backDispatcher, block printState) } Composable fun Two(backDispatcher: OnBackPressedDispatcher, block: () - Unit) { val blockState by rememberUpdatedState(block) val backCallback remember { //重写返回键的监听回调 object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { blockState() } } } LaunchedEffect(Unit) { //传入Unit当key不会随重组而重新执行 backDispatcher.addCallback(backCallback) //设置回调 } } //Activity中调用并传入 //One(onBackPressedDispatcher)2.6 produceState()Composablefun T produceState(initialValue: T,key1: Any?,key2: Any?,producer: suspend ProduceStateScopeT.() - Unit): StateT//返回值非Unit的组合函数名采用常规小写开头 //返回值类型是 StateImageResultImageBitmap //key值变化都会重新执行block Composable fun demo(url: String, repository: ImageRepository) produceState( initialValue ImageResult.Loading as ImageResultImageBitmap, key1 url, key2 repository ) { val image repository.loadImage(url) //调用挂起函数 value if (image null) ImageResult.Error else ImageResult.Success(image) //挂起父协程好让自己一直执行直到被取消 awaitCancellation() //释放资源 } sealed interface ImageResultT { object Loading : ImageResultImageBitmap object Error : ImageResultImageBitmap data class Success(val imageBitmap: ImageBitmap) : ImageResultImageBitmap } class ImageRepository { suspend fun loadImage(url: String): ImageBitmap? withContext(Dispatchers.IO) { null } }2.7 derivedStateOf()fun T derivedStateOf(calculation: () - T,): StateT//每次重组遍历titles看是否包含关键字的标题非常耗性能 //只在每次更新titles的时候去遍历过滤出包含关键字的标题 Composable fun Demo( keywords:ListString listOf(关键字1, 关键字2, 关键字3) ) { val titles remember { mutableStateListOfString() } val result remember(keywords) { derivedStateOf { titles.filter { keywords.contains(it) } } } LazyColumn(modifier Modifier.fillMaxWidth()) { items(result) { ... } //包含关键字的标题的列表 items(titles) { ... } //全部标题的列表 } }Composable fun Demo() { val list remember { mutableStateListOfString() } val showText by remember { derivedStateOf{ list.size.toString() } } }2.8 snapshotFlow()fun T snapshotFlow(block: () - T): FlowTval listState rememberLazyListState() LazyColumn(state listState) { items(100) { Text(text Item $it) } } LaunchedEffect(listState) { snapshotFlow { listState.firstVisibleItemIndex } .filter { it 20 } //从索引20开始收集 .distinctUntilChanged() .collect {...} }
延伸阅读

更多相关文章

2026/11/4 6:40:40

clamav升级问题报错2:Can‘t query current.cvd.clamav.net

👨‍🎓博主简介 🏅云计算领域优质创作者   🏅华为云开发者社区专家博主   🏅阿里云开发者社区专家博主 💊交流社区:运维交流社区 欢迎大家的加入! 🐋 希望大家多多支…

2026/10/31 1:39:09

SQL Server 2016更改sa用户名

1:sa是数据库的默认用户名,出于安全考虑,最好更改默认的用户名。2:运行SQL Server管理工具,连接数据库。3:在“安全性”下的“登录名”中找到sa。4:右键选择sa,重命名,改…

2026/11/4 6:38:56

Komikku漫画阅读器:从技术架构到用户体验的深度解析

Komikku漫画阅读器:从技术架构到用户体验的深度解析 在移动漫画阅读领域,用户体验与资源覆盖往往是难以兼顾的痛点。Komikku作为一款基于Kotlin技术栈的开源Android应用,通过创新的技术架构设计,实现了海量漫画源聚合与个性化阅读…

2026/11/4 6:38:56

HS100-N伺服驱动器调试 电子齿轮设置和计算

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/4 6:38:56

Hermes Agent:3步打造你的AI金融分析专家,让投资决策更智能

Hermes Agent:3步打造你的AI金融分析专家,让投资决策更智能 在数字化投资时代,你是否曾梦想拥有一个全天候工作的金融分析师助手?Hermes Agent正是这样一个能与你共同成长的自学习AI代理框架。它不仅支持300多种AI模型&#xff0…

2026/11/4 6:38:56

视觉评估新范式:VisionReward-Image如何破解AI创作的质量迷雾

视觉评估新范式:VisionReward-Image如何破解AI创作的质量迷雾 【免费下载链接】VisionReward-Image-bf16 项目地址: https://ai.gitcode.com/zai-org/VisionReward-Image-bf16 当AI生成图像的数量每18个月翻一番时,人类却陷入了"审美瘫痪&q…

2026/11/3 7:47:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/3 14:24:53

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/11/4 0:03:20

AI辅助技术栈评估:提升研发决策效率60%

1. 项目背景与核心价值技术决策一直是研发团队面临的重大挑战。随着技术生态的快速演进,技术栈的选择、维护和迭代变得愈发复杂。我们团队在过去两年里,先后经历了三次重大技术栈迁移,每次都需要投入大量人力进行技术评估和风险分析。直到去年…

2026/11/4 0:03:20

从生成Token到直接决策:Jev如何重塑AI Agent工程实践

1. 从"生成文字"到"拍板决策":Jev 到底在解决什么问题大多数人第一次接触大模型,脑子里建立的模型是"输入问题、输出文字"。你问它今天天气怎么样,它给你一段描述;你让它写个周报,它给你…

2026/11/4 0:03:20

32G统一内存跑大模型:内存账、TPS实测与端云分流

想在一台 32G 统一内存的小主机上把大模型跑成"日常可用",这件事我前后折腾了小半年。标题里说的这台 Mac mini M6,是我们这次讨论的目标平台,具体到手规格以实物为准;但下面这套推算方法和实测思路,对任何统…

2026/11/3 7:47:06

面向复杂光照与角度的宠物面部识别鲁棒性增强策略

在智慧养宠时代,基于视觉的宠物身份识别技术正逐步从实验室走向家庭应用。然而,与配合度高的人类不同,宠物是典型的“非合作生物识别对象”——它们不会主动配合摄像头调整姿态,更无法控制光照环境。实际应用中,我们常…

2026/11/3 14:24:53

SSM电子书店源码全解析:结构、原理、部署与排错实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑