Skip to content
Maozy's Blog
Go back

[Jetpack Compose] 副作用(Side Effects)和 LaunchedEffect

一、LaunchedEffect

提问:如果我想在页面一打开的时候,去发起一个网络请求拉取数据,或者弹Toast,这段代码应该写在哪里?

1.1 错误示范(直接写在函数里)

@Composable
fun UserProfile(userId: String) {
    // 只要界面发生任何微小的刷新(重组),
    // 这个函数就会被重新执行,网络请求会被疯狂发送无数次!
    fetchUserDataFromNetwork(userId) 

    Text("用户信息")
}

在纯函数里执行这种“与 UI 渲染无关、且只应该执行一次或特定次数的操作”,就叫做副作用(Side Effect)

1.2 正确的解决思路:使用 LaunchedEffect

我们需要一个“避风港”,告诉 Compose:“这段逻辑虽然写在 Composable 函数里,但请你把它隔离开,它有自己的生命周期。”

@Composable
fun UserProfile(userId: String) {
    // LaunchedEffect 就是专门用来处理协程副作用的 API
    LaunchedEffect(key1 = userId) {
        // 这里的代码会在组件第一次显示到屏幕上时执行一次。
        // 如果 userId 变了,它会取消之前的请求,重新执行一次。
        // 其他任何无关的重组,都不会导致这里重新执行
        fetchUserDataFromNetwork(userId) 
    }
    Text("用户信息")
}

1.3 总结

因为 Composable 函数可能会在重组时被频繁、乱序地执行,所以绝对不能在组件中直接执行非纯函数的逻辑(如网络请求、数据库操作)。

必须使用 Compose 提供的副作用 API(如 LaunchedEffect,将其桥接到协程作用域中,并依赖特定的 Key 来控制执行时机,从而保证程序的稳定性。

二、LaunchedEffect 和协程

2.1 LaunchedEffect是什么

LaunchedEffect 不仅是“配合”协程,它本身就是一个协程启动器

在纯 Kotlin 协程里,我们要启动一个协程,通常需要写 scope.launch { ... }

在 Compose 中,LaunchedEffect 的底层源码就是帮我们做了一件事:在组件进入屏幕(Enter the Composition)时,自动给我们提供一个安全的 CoroutineScope,并帮我们执行 launch

2.2 为什么一定要跟“组件生命周期”绑定?

以前用 Java 开发 Android 的噩梦:

在一个 Activity 里发起了一个网络请求(用 Thread 或网络库),请求还没回来,用户按了返回键退出了页面(Activity 被销毁)。如果这时候请求回来了,还要去更新 UI,就会直接导致 Crash(NullPointerException 或 WindowLeaked)。为了解决这个问题,以前我们需要在 onDestroy() 里手动去 cancel 所有的请求。

Compose 的优雅处理: 在 Compose 里,组件没有 onDestroy。一个 @Composable 函数可能因为一个 if 条件为 false,就直接从屏幕上消失了(这叫 Leave the Composition)。

这时候 LaunchedEffect 就起作用了:

2.3 LaunchedEffect 的 Key 机制(重启机制)

考虑这样一个衍伸场景::“如果组件还没销毁,但是我想重新请求一次数据怎么办?”

这就要看 LaunchedEffect 的参数了,比如 LaunchedEffect(userId) { ... }

这里 userId 就是 Key(重启键)。 它的内部逻辑极其精妙:

  1. 进入组件,传入 userId = "张三",开启协程拉取张三的数据。
  2. 发生重组(比如点击了切换按钮),传入的 userId 变成了 "李四"
  3. LaunchedEffect 发现 Key 发生变化了
    • 它会立刻取消之前拉取张三数据的那个协程;

    • 然后重新启动一个新的协程去拉取李四的数据。

2.4 总结

在 Compose 中如何做网络请求?Side Effect 是干什么的?”

在 Compose 中发起网络请求等挂起操作,必须使用 LaunchedEffect 这样的 Side Effect API。

它的本质是提供了一个与当前 Composable 生命周期强绑定的协程作用域 (CoroutineScope)

当组件进入组合时,它会启动协程;当组件离开组合时,它会自动取消协程,从根本上杜绝了内存泄漏和异步回调更新 UI 导致的 Crash。

此外,它还自带基于 Key 的重启机制。只要传入的 Key 发生改变,它会自动取消旧任务并启动新任务,这对于列表详情页的数据刷新场景非常契合。


Share this post on:

Previous Post
[PocketAgent 实战] Day1 Python SSE 与网络背压机制
Next Post
[Jetpack Compose] 纯Compose VS 传统Android