Compose 实践与探索十二 —— 附带效应

鼠扑  论坛元老 | 2025-3-17 13:02:13 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1029|帖子 1029|积分 3087

1、SideEffect

Google 官方文档对 side effect 有两种翻译,简体中文翻译为附带效应,繁体中文翻译为副作用。这两个翻译我们用哪个都行,关键是如何理解它的含义。
1.1 什么是副作用

我们在一样平常生活中听到的副作用大多是医学领域中的,药物的副作用是指除了预期治疗效果外,在利用过程中可能产生的额外不良反应或不良影响。可以理解为,副作用是目的作用为了见效而附带而来的作用,它并不是“负作用”那种完全欠好的、负面的作用。
在 Compose 中,副作用(或附带效应,后续出现两个词都以为是 side effect)通常指的是对界面以外的状态举行更改大概操纵的行为。在函数式编程中,强调函数的纯粹性,即函数的输出仅依靠于输入,不会对外部状态产生影响。然而,在 UI 开发中,通常需要与外部情况举行交互,比如修改变量、举行网络哀求等,这些会导致“副作用”。
比如说:
  1. fun a() {
  2.     var value = 0
  3.     value = 1
  4. }
  5. var flag = false
  6. fun b() {
  7.     flag = true
  8. }
复制代码
函数 a 只修改了其内部的变量,因此没有副作用;而函数 b 修改了外部的变量 flag,因此它有副作用。
再看:
  1. fun c() {
  2.     println("Compose")
  3. }
复制代码
想要确认函数 c 是否有副作用,通过判断是否修改函数内部变量的方式似乎不能确定,这里我们可以借助副作用的学术性定义:对于一个函数,如果用它的返回值替换函数本身,但不会对步伐有任何影响,那么该函数就没有副作用,如果产生了影响,两种效果之间的差异就是副作用。
对于 println() 这个函数而言,它本身没有返回值,大概说是一个 Unit,用返回值替换函数本身使得无法打印指定内容,这对步伐产生了影响,因此 println 这个函数是有副作用的,函数 c 也因此是有副作用的。
副作用这个词可以很好地描述函数的纯净性,可以称一个函数是“有/无副作用的函数”。Compose 就要求所有的组件函数都是无副作用的函数。Compose 的 @Composable 函数是用来显示界面内容的,应该只包罗界面显示工作,不应掺杂其他任何对外界有影响的工作,也就是不应该有副作用。因为 @Composable 函数的副作用会导致整个步伐产生不可预期的效果,这是由于 @Composable 函数的调用就具有不可预期性。
由于 Compose 框架对于重组过程的优化,一个 @Composable 函数可能在运行过程中被终断甚至干脆就没被实行,如允许能会出现影响外界的代码,有一部分被实行了,剩余的部分由于终断而没被实行,从而产生不可预期的效果。
此外,由于重组的次数不确定,具有副作用的 @Composable 函数的实行效果也是不可预期的:
  1.                 setContent {
  2.             var seasonCount = 0
  3.             Column {
  4.                 val seasons = arrayOf("Spring", "Summer", "Autumn", "Winter")
  5.                 seasons.forEach {
  6.                     Text(it)
  7.                     // 在重组之后确定 Column 仍会显示才执行代码块的内容,但此时
  8.                     // 最下面的 Text 要显示的内容已经确定了,无法再更改
  9.                     SideEffect {
  10.                             seasonCount++   
  11.                     }
  12.                 }
  13.                 Text("Total season count: $seasonCount")
  14.             }
  15.         }
复制代码
按照假想,最后 Text 展示的 seasonCount 应该为 4,但如果代码运行过程中出现多次重组,那么 seasonCount 就不会是 4 了,效果不在预期中。
正是因为种种的不可预期性,Compose 建议开发者不要在 @Composable 函数中引入副作用代码。但很多时间,业务需求使得我们无法服从这个建议,比如在代码中埋点统计函数的实行数据,那难道因为 Compose 的建议就无法完成这些业务需求吗?当然不是,任何时间,业务都是优先的,没有贸易和业务支撑的代码创造不出任何代价,也就一文不值。
1.2 SideEffect 函数

Compose 提供了一些函数来满意业务需求(比如埋点),最直接与最简单的就是 SideEffect()。
SideEffect() 内的代码不会在实行到它时立刻被实行,而是先被生存起来举行等待,直到本轮重组过程完成,确定了 SideEffect 所在的组件会在界面上显示,才会实行其内部代码。如允许以保证没有实行完就被取消的 Composable 函数的副作用代码不会被实行,还可保证在一轮重组过程中被多次调用的 Composable 函数的代码只被实行一次。
那利用 SideEffect 是不是能办理所有的副作用相关的问题呢?当然不是,想通过 SideEffect 办理副作用问题有一个前提,就是引入副作用代码的这个需求必须是正常的,不能对外界造成不可预期影响的需求。比如照旧上面的例子,由于对 seasonCount 的自增操纵会对外界造成不可预期的影响,因此只是简单的为它包上一层 SideEffect 并不能到达预期的效果:
  1.                 setContent {
  2.             var seasonCount = 0
  3.             Column {
  4.                 val seasons = arrayOf("Spring", "Summer", "Autumn", "Winter")
  5.                 seasons.forEach {
  6.                     Text(it)
  7.                     // 在重组之后确定 Column 仍会显示才执行代码块的内容,但此时最下面的 Text
  8.                     // 要显示的内容已经确定了,无法再更改,再运行 seasonCount++ 已经晚了
  9.                     SideEffect {
  10.                             seasonCount++   
  11.                     }
  12.                 }
  13.                 Text("Total season count: $seasonCount")
  14.             }
  15.         }
复制代码
真正的办理办法是你要把业务(可以简单的理解为数据处理)与界面显示分拆,在显示 UI 前把数据准备好,而不是滥用 SideEffect:
  1.                 setContent {
  2.             var seasonCount = 0
  3.             Column {
  4.                 val seasons = arrayOf("Spring", "Summer", "Autumn", "Winter")
  5.                 seasonCount = seasons.size()
  6.                 seasons.forEach {
  7.                     Text(it)
  8.                     /*SideEffect {
  9.                             seasonCount++   
  10.                     }*/
  11.                 }
  12.                 Text("Total season count: $seasonCount")
  13.             }
  14.         }
复制代码
2、DisposableEffect

DisposableEffect 是 SideEffect 的升级版,增加了对离开界面的监听。比如:
  1.     Button(onClick = { /*TODO*/ }) {
  2.         DisposableEffect(Unit) {
  3.             // Button 进入页面监听
  4.             println("Button 进入页面")
  5.             // 该 lambda 表达式必须返回一个 DisposableEffectResult,可以通过
  6.             // 主动调用组件离开页面的监听函数 onDispose() 得到
  7.             onDispose { println("Button 离开页面") }
  8.         }
  9.     }
复制代码
通过自动调用 onDispose() 设置对组件离开页面的监听内容,如许在所属组件离开页面(准确点说是离开组合 Composition)时就会回调 onDispose(),同理进入页面会回调 DisposableEffect() 的内容。
有两种场景实用 DisposableEffect:

  • 埋点,统计用户进入以及退出了哪些界面
  • 组件进入页面时在 DisposableEffect() 内为该组件设置监听器,组件离开页面时在 onDispose() 内取消监听器
此外,我们要看一下 DisposableEffect 的第一个参数 key1:
  1. @Composable
  2. @NonRestartableComposable
  3. fun DisposableEffect(
  4.     key1: Any?,
  5.     effect: DisposableEffectScope.() -> DisposableEffectResult
  6. ) {
  7.     remember(key1) { DisposableEffectImpl(effect) }
  8. }
复制代码
它的作用是,当传入 DisposableEffect() 的 key1 发生厘革时,整个 DisposableEffect() 会举行一次重启,重启动作包括两步:

  • 先对老的 key1 值实行一次离开回调 onDispose()
  • 然后对新的 key1 值实行一次 effect 参数函数中的进入回调
如许的顺序可以保证步伐有一个合理的实行过程。比如 onDispose() 中会实行将对象 A 置为 null 的操纵,而 DisposableEffect() 会为 A 赋值。那么当 DisposableEffect 因为 key1 的厘革而重启时,就会先将 A 置为 null 然后再为它赋新值,而不是先为 A 赋了新值再置为 null。
反之,如果 key1 不变,岂论 DisposableEffect 所在的组件如何举行重组,DisposableEffect 都不会重启(避免资源消耗):
  1. @Composable
  2. fun DisposableEffectSample1() {
  3.     var showText by remember { mutableStateOf(false) }
  4.     Button(onClick = { showText = !showText }) {
  5.         Text("点击")
  6.         if (showText) {
  7.             Text("Compose")
  8.         }
  9.         // 只要 Button 重组就会回调
  10.         SideEffect {
  11.             println("SideEffect")
  12.         }
  13.         // key1 不变不论 Button 如何重组,DisposableEffect 都不会重启
  14.         DisposableEffect(Unit) {
  15.             println("Button 进入页面")
  16.             onDispose { println("Button 离开页面") }
  17.         }
  18.     }
  19. }
复制代码
不断点击 Button 让其发生重组,但是只有 SideEffect() 会跟随重组举行重启,由于 DisposableEffect() 的 key1 参数不变,所以只有首次进入页面时的 log 被输出:
  1. Button 进入页面
  2. SideEffect
  3. SideEffect
  4. SideEffect
复制代码
但将 DisposableEffect() 的 key1 参数换为 showText 之后,点击按钮会触发 DisposableEffect() 重启:
  1. Button 进入页面
  2. SideEffect
  3. Button 离开页面
  4. Button 进入页面
  5. SideEffect
  6. Button 离开页面
  7. Button 进入页面
  8. SideEffect
复制代码
并且你能看到,DisposableEffect 会先触发 onDispose() 回调,再回调本身内部的代码。
3、LaunchedEffect

LaunchedEffect 会在 Composable 组件完成显示之后启动协程,并在参数发生改变之后重启协程。
LaunchedEffect 从功能与底层实现上来讲是特殊形式的 DisposableEffect:
  1. @Composable
  2. @NonRestartableComposable
  3. fun DisposableEffect(
  4.     key1: Any?,
  5.     effect: DisposableEffectScope.() -> DisposableEffectResult
  6. ) {
  7.     remember(key1) { DisposableEffectImpl(effect) }
  8. }
复制代码
key1 厘革才会实行 DisposableEffectImpl():
  1. private class DisposableEffectImpl(
  2.     private val effect: DisposableEffectScope.() -> DisposableEffectResult
  3. ) : RememberObserver {
  4.     private var onDispose: DisposableEffectResult? = null
  5.     override fun onRemembered() {
  6.         onDispose = InternalDisposableEffectScope.effect()
  7.     }
  8.     override fun onForgotten() {
  9.         onDispose?.dispose()
  10.         onDispose = null
  11.     }
  12.     override fun onAbandoned() {
  13.         // Nothing to do as [onRemembered] was not called.
  14.     }
  15. }
复制代码
而 LaunchedEffect 也是在 key1 厘革时才实行 LaunchedEffectImpl():
  1. @Composable
  2. @NonRestartableComposable
  3. @OptIn(InternalComposeApi::class)
  4. fun LaunchedEffect(
  5.     key1: Any?,
  6.     block: suspend CoroutineScope.() -> Unit
  7. ) {
  8.     val applyContext = currentComposer.applyCoroutineContext
  9.     remember(key1) { LaunchedEffectImpl(applyContext, block) }
  10. }
复制代码
LaunchedEffectImpl 也实现了 RememberObserver,并且实现内容都是基于协程的:
  1. internal class LaunchedEffectImpl(
  2.     parentCoroutineContext: CoroutineContext,
  3.     private val task: suspend CoroutineScope.() -> Unit
  4. ) : RememberObserver {
  5.     private val scope = CoroutineScope(parentCoroutineContext)
  6.     private var job: Job? = null
  7.     override fun onRemembered() {
  8.         job?.cancel("Old job was still running!")
  9.         job = scope.launch(block = task)
  10.     }
  11.     override fun onForgotten() {
  12.         job?.cancel()
  13.         job = null
  14.     }
  15.     override fun onAbandoned() {
  16.         job?.cancel()
  17.         job = null
  18.     }
  19. }
复制代码
何时会用到?实际上就是把组件显示到界面作为某种业务的触发逻辑时,实际上 DisposableEffect 也是这种逻辑,只不外 LaunchedEffect 是面向协程的。比如,某个组件在页面中显示 3 秒钟后消散(如刚进入视频播放页面后,视频播放的控制面板会显示几秒后消散)。
4、rememberUpdatedState

依靠但又不盼望重启协程
上两节我们分别讲了 DisposableEffect 与 LaunchedEffect,它俩有一个共同的功能,就是根据参数上传入的 key 是否发生厘革决定是否重启自身的实行。即参数 key 变了就重启,不变的话即便所在组件重组也不会重启,避免资源消耗。
以往我们碰到的大多数情况,都是组件依靠的状态发生厘革会在同一帧中立刻起到作用,比如以下这种极度简化过的代码:
  1. var text by remember { mutableStateOf("Compose") }
  2. ...
  3. Text(text)
复制代码
但有一些场景下,我们盼望被依靠的状态发生改变时,不去触发重组或 DisposableEffect 与 LaunchedEffect 的重启,也能在需要利用这个状态时拿到它最新的值:
  1. @Composable
  2. fun RememberUpdatedStateSample() {
  3.     var welcome by remember { mutableStateOf("Initial value.") }
  4.     Button(onClick = { welcome = "Jetpack Compose" }) {
  5.         Text("点击")
  6.         LaunchedEffect(Unit) {
  7.             delay(3000)
  8.             println("welcome: $welcome")
  9.         }
  10.     }
  11. }
复制代码
如果在 LaunchedEffect 的 3 秒延时之内点击按钮,那么 welcome 在打印时会输出更新后的 “Jetpack Compose”,而不是初始值。这种更新不需要将 welcome 作为 LaunchedEffect 的参数,在可以获取到 welcome 新值的同时,还避免了 LaunchedEffect 重启带来的性能损耗。
但如果将 LaunchedEffect 抽取到一个单独的函数中,即便在 3 秒内点击按钮,welcome 也只打印初始值:
  1. @Composable
  2. fun RememberUpdatedStateSample() {
  3.     var welcome by remember { mutableStateOf("Initial value.") }
  4.     Button(onClick = { welcome = "Jetpack Compose" }) {
  5.         Text("点击")
  6.         CustomLaunchedEffect(welcome)
  7.     }
  8. }
  9. @Composable
  10. private fun CustomLaunchedEffect(welcome: String) {
  11.     LaunchedEffect(Unit) {
  12.         delay(3000)
  13.         println("welcome: $welcome")
  14.     }
  15. }
复制代码
为什么第一种情况可以,第二种情况不行呢?因为第一种情况的 welcome 通过 remember() + mutableStateOf() 实现了一个长期存储且可以将状态厘革通知到所有利用处的变量,因此它可以跨越重组通报到 Button 的内部,在发生厘革时可以同步给 LaunchedEffect()。而第二种情况,将 welcome 作为函数参数通报,那么 CustomLaunchedEffect() 中的 welcome 就是一个普通的变量,它的厘革不会同步给 LaunchedEffect() 内的 welcome,因此即便给 CustomLaunchedEffect() 的传参发生了厘革,但打印输出的 welcome 仍是协程最初拿到的初始值。
那如何办理呢?把 welcome 填到 LaunchedEffect() 的参数上?我们的要求是尽量避免让 LaunchedEffect() 重启,因此如许不行。所以照旧效仿第一种情况,用 remember() + mutableStateOf() 构造一个状态变量,然后把参数 welcome 传给该状态变量:
  1. @Composable
  2. private fun CustomLaunchedEffect(welcome: String) {
  3.     var rememberedWelcome by remember { mutableStateOf(welcome) }
  4.     rememberedWelcome = welcome
  5.     LaunchedEffect(Unit) {
  6.         delay(3000)
  7.         println("welcome: $rememberedWelcome")
  8.     }
  9. }
复制代码
LaunchedEffect() 之前的两个语句可以用 rememberUpdatedState() 平替:
  1. @Composable
  2. fun <T> rememberUpdatedState(newValue: T): State<T> = remember {
  3.     mutableStateOf(newValue)
  4. }.apply { value = newValue }
复制代码
也就是:
  1. @Composable
  2. private fun CustomLaunchedEffect(welcome: String) {
  3.     val rememberedWelcome by rememberUpdatedState(welcome)
  4.     LaunchedEffect(Unit) {
  5.         delay(3000)
  6.         println("welcome: $rememberedWelcome")
  7.     }
  8. }
复制代码
rememberUpdatedState() 除了可以用于 LaunchedEffect(),也可用于 DisposableEffect(),比如:
  1. @Composable
  2. fun CustomDisposableEffect(user: User) {
  3.     DisposableEffect(Unit) {
  4.         // 拿不到 user 的新值
  5.         suscriber.subscribe(user)
  6.         onDispose {
  7.             suscriber.unsubscribe()
  8.         }
  9.     }
  10. }
复制代码
在 DisposableEffect() 举行订阅操纵时,拿不到参数 user 的新值,因此照旧要利用 rememberUpdatedState() 来办理:
  1. @Composable
  2. fun CustomDisposableEffect(user: User) {
  3.     val updatedUser by rememberUpdatedState(user)
  4.     DisposableEffect(Unit) {
  5.         suscriber.subscribe(user)
  6.         onDispose {
  7.             suscriber.unsubscribe()
  8.         }
  9.     }
  10. }
复制代码
5、rememberCoroutineScope

rememberCoroutineScope() 是在 Compose 中除了 LaunchedEffect() 之外,另一种利用协程的方式。
在 Compose 中利用协程不能像通用的协程利用方法那样,比如不可以直接利用 lifecycleScope.launch(),因为 lifecycleScope 作为一个 CoroutineScope 是用来管理协程的,重要负责在与它绑定的具有生命周期的组件结束后,自动结束该组件中运行的协程。而 Composable 函数也是具有声明周期的,在 Composable 函数内启动的协程也应该在函数结束后自动结束,这意味着每个 Composable 函数都有本身的 CoroutineScope。
因此在 Composable 函数中应该利用相应的 CoroutineScope,而不是与 Activity 生命周期绑定的 lifecycleScope。利用 rememberCoroutineScope() 可以获取到与当前组合点绑定的 CoroutineScope,然后在 remember() 中可以直接用它启动协程:
  1. val coroutineScope = rememberCoroutineScope()
  2. // 不用 remember 包上 launch() 会报错,因为遇到重组时每次都会重新启动一次协程
  3. val coroutine = remember { coroutineScope.launch { } }
复制代码
同样是在 Compose 中启动一个协程,LaunchedEffect() 的内部实际上已经为利用者完成了 CoroutineScope 的获取与 remember() 的利用:
  1. @Composable
  2. @NonRestartableComposable
  3. @OptIn(InternalComposeApi::class)
  4. fun LaunchedEffect(
  5.     key1: Any?,
  6.     block: suspend CoroutineScope.() -> Unit
  7. ) {
  8.     val applyContext = currentComposer.applyCoroutineContext
  9.     remember(key1) { LaunchedEffectImpl(applyContext, block) }
  10. }
复制代码
因此,通常我们利用 LaunchedEffect() 启动协程就足够了。但如果想要在 Composable 组件的外面启动协程时,需要利用 rememberCoroutineScope():
  1. val coroutineScope = rememberCoroutineScope()
  2. // 点击 Box 触发 clickable 回调时才启动协程,这是在组件外部启动的协程
  3. Box(Modifier.clickable { coroutineScope.launch { } })
复制代码
6、协程或其他状态向 Compose 状态的转换

本节重要讲如何将非 Compose 状态转换为 Compose 状态。
6.1 DisposableEffect

之前说过 DisposableEffect() 可以用来做一些订阅工作,并且可以在它的 onDispose() 回调中取消订阅。这种用法也可以用在订阅数据更新上,比如说舆图上要显示一个坐标点,当坐标数据发生厘革时 UI 应自动更新:
  1. val geoManager: GeoManager = GeoManager()
  2. @Composable
  3. fun UpdatePoint() {
  4.     var position by remember { mutableStateOf(Point(0, 0)) }
  5.     DisposableEffect(Unit) {
  6.         // PositionCallback 提供最新的坐标数据 newPos
  7.         val callback = object : PositionCallback { newPos ->
  8.             position = newPos
  9.         }
  10.         // 注册回调与取消回调注册
  11.         geoManager.register(callback)
  12.         onDispose {
  13.             // 本组件不再显示时取消注册
  14.             geoManager.unregister(callback)
  15.         }
  16.     }
  17. }
复制代码
PositionCallback 可以提供更新后的坐标数据,而 GeoManager 在注册回调后可以吸收到坐标厘革,这个厘革的坐标 newPos 本来是 Compose 无法识别的普通变量,经过赋值给 position 状态后,newPos 的厘革可以自动应用到界面上,这就是一种将普通数据转换为 Compose 状态的简单示例。
此外,相同的套路也可用在 LiveData 转换为 State 上:
  1. val positionData = MutableLiveData<Point>()
  2. @Composable
  3. fun UpdatePoint(owner: LifecycleOwner) {
  4.     var position by remember { mutableStateOf(Point(0, 0)) }
  5.     DisposableEffect(Unit) {
  6.         val observer = Observer<Point> { newPos ->
  7.             position = newPos
  8.         }
  9.         positionData.observe(owner, observer)
  10.         onDispose {
  11.             positionData.removeObserver(observer)
  12.         }
  13.     }
  14. }
复制代码
实际上,Compose 为 LiveData 提供了扩展函数 observeAsState() 就可以将 LiveData 转换为 State:
  1. // 需依赖 androidx.compose.runtime:runtime-livedata 方可使用
  2. @Composable
  3. fun <T> LiveData<T>.observeAsState(): State<T?> = observeAsState(value)
  4. @Composable
  5. fun <R, T : R> LiveData<T>.observeAsState(initial: R): State<R> {
  6.     val lifecycleOwner = LocalLifecycleOwner.current
  7.     // 用初始值创建一个 State 对象
  8.     val state = remember { mutableStateOf(initial) }
  9.     DisposableEffect(this, lifecycleOwner) {
  10.         // 更新 state 值的 Observer
  11.         val observer = Observer<T> { state.value = it }
  12.         // 订阅
  13.         observe(lifecycleOwner, observer)
  14.         // 取消订阅
  15.         onDispose { removeObserver(observer) }
  16.     }
  17.     return state
  18. }
复制代码
6.2 LaunchedEffect

对于用到了协程的外部状态,如 Flow,就不能用 DisposableEffect 举行转换了,而是要换成 LaunchedEffect:
  1. val positionState: StateFlow<Point> = TODO()
  2. @Composable
  3. fun UpdatePoint(owner: LifecycleOwner) {
  4.     var position by remember { mutableStateOf(Point(0, 0)) }
  5.     LaunchedEffect(Unit) {
  6.         positionState.collect { newPos ->
  7.             position = newPos
  8.         }
  9.     }
  10. }
复制代码
6.3 produceState()

produceState() 创建一个 MutableState 对象并在协程中更新它的值:
  1. @Composable
  2. fun <T> produceState(
  3.     initialValue: T,
  4.     @BuilderInference producer: suspend ProduceStateScope<T>.() -> Unit
  5. ): State<T> {
  6.     val result = remember { mutableStateOf(initialValue) }
  7.     LaunchedEffect(Unit) {
  8.         ProduceStateScopeImpl(result, coroutineContext).producer()
  9.     }
  10.     return result
  11. }
复制代码
参数 producer 内定义获取状态值的代码,它会在协程中被实行用于获取最新的状态值:
  1. val positionState: StateFlow<Point> = TODO()
  2. @Composable
  3. fun UpdatePoint(owner: LifecycleOwner) {
  4.     // 参数传入初始值
  5.     val produceState = produceState(Point(0, 0)) {
  6.         positionState.collect {
  7.             // Flow 传来的新数据 it 赋值给 State 的真实数据对象 value
  8.             value = it
  9.         }
  10.     }
  11. }
复制代码
相当于把 LaunchedEffect() 的写法封装到 produceState() 这个便捷函数中了。
produceState() 内还可以调用一个 awaitDispose(),它可以无限期挂起协程,重要用于转换不是协程提供的状态的情况。
最后要提一嘴,StateFlow 提供了扩展函数 collectAsState() 可以直接将一个 StateFlow 转换成 State:
  1. @Suppress("StateFlowValueCalledInComposition")
  2. @Composable
  3. fun <T> StateFlow<T>.collectAsState(
  4.     context: CoroutineContext = EmptyCoroutineContext
  5. ): State<T> = collectAsState(value, context)
  6. @Composable
  7. fun <T : R, R> Flow<T>.collectAsState(
  8.     initial: R,
  9.     context: CoroutineContext = EmptyCoroutineContext
  10. ): State<R> = produceState(initial, this, context) {
  11.     if (context == EmptyCoroutineContext) {
  12.         collect { value = it }
  13.     } else withContext(context) {
  14.         collect { value = it }
  15.     }
  16. }
复制代码
它内部就是用到了 produceState()。
7、把 Compose 的 State 转换成协程的 Flow

snapshotFlow() 可以把 Compose 的 State 转换成协程 Flow:
  1.                 setContent {
  2.             var name by remember { mutableStateOf("Jack") }
  3.             var age by remember { mutableStateOf(18) }
  4.             val flow = snapshotFlow { "$name $age" }
  5.             LaunchedEffect(Unit) {
  6.                 // snapshotFlow() 内任何一个状态发生变化,都会以新值执行一次 collect
  7.                 flow.collect { info ->
  8.                     println(info)
  9.                 }
  10.             }
  11.         }
复制代码
在Compose中,副作用通常发生在LaunchedEffect、DisposableEffect、SideEffect等函数中。这些函数用于处理可能会引起副作用的操纵,如启动协程、订阅数据、修改可变状态等。需要注意的是,在Compose中,副作用应该尽量被限定在特定的作用域内,以保持代码的可维护性和可预测性。
总的来说,Compose中的“副作用”指的是对外部状态举行更改或操纵的行为,通过符合的方式管理和控制副作用的产生,可以帮助确保应用的正确性和性能。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

鼠扑

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表