一、什么是副作用(Side Effects)
1、1 问题背景
在 Compose 中,Composable函数应该是纯函数——相同输入产生相同输出,不应该有副作用。但实际开发中,我们经常需要:
- 🌐 网络请求:在界面显示时加载数据
- 📊 事件订阅:监听传感器、数据库变化
- ⏰ 定时任务:启动倒计时、轮询
- 🗑️ 资源清理:取消订阅、释放资源
这些操作都属于副作用(Side Effects)——它们会影响外部状态或系统资源。
1、2 为什么需要 Side Effects API?
// ❌ 错误示例:直接在 Composable 中执行副作用
@Composable
fun BadExample() {
// 问题1:每次重组都会启动新协程,导致协程泄漏
CoroutineScope(Dispatchers.IO).launch {
loadData()
}
// 问题2:没有清理机制,离开组合后订阅依然存在
sensorManager.registerListener(...)
}
Compose 的重组特性导致以上代码存在严重问题:
- 重组频繁:可能每帧都重组,副作用会重复执行
- 生命周期不确定:可能随时从组合中移除
- 资源泄漏:没有清理机制导致内存泄漏
副作用解决了这些问题:
- ✅ 提供生命周期管理
- ✅ 自动清理资源
- ✅ 控制副作用执行时机
- ✅ 避免重复执行
副作用API包括LaunchedEffect、DisposableEffect、rememberCoroutineScope、rememberUpdatedState、SideEffect。
二、核心概念:RememberObserver
除了 SideEffect,其它的都基于 RememberObserver 接口实现。
2、1 RememberObserver 接口
// 位置:androidx.compose.runtime.RememberObserver
interface RememberObserver {
/**
* 当对象成功进入组合时调用(进入组合)
*/
fun onRemembered()
/**
* 当对象离开组合时调用
*/
fun onForgotten()
/**
* 当对象创建后但组合被放弃时调用
*/
fun onAbandoned()
}
三、LaunchedEffect
3、1 为什么不能用 remember + launch?
用kotlin开发,就会用协程发起网络请求,直接在Compose函数中使用协程发起网络请求,下面的代码存在两个问题:
- 问题1:userId 变化时不会取消旧协程。
- 问题2:离开组合时协程不会被取消
// ❌ 错误做法
@Composable
fun BadExample(userId: String) {
val scope = remember { CoroutineScope(Dispatchers.IO) }
// 问题1:userId 变化时不会取消旧协程
scope.launch {
loadData(userId)
}
// 问题2:离开组合时协程不会被取消
}
正确的做法是使用LaunchedEffect。
// ✅ 正确做法
@Composable
fun GoodExample(userId: String) {
LaunchedEffect(userId) {
// ✅ userId 变化自动取消旧协程
// ✅ 离开组合自动取消
loadData(userId)
}
}
LaunchedEffect在首次组合会创建协程,重组的时候,如果key没有发生变化,就不会创建协程。像初始化等只需执行一次的代码,就需要使用LaunchedEffect。
3、2 LaunchedEffect原理
看到了熟悉的remember函数,之前文章中介绍过remember函数的原理。
public fun LaunchedEffect(key1: Any?, block: suspend CoroutineScope.() -> Unit) {
val applyContext = currentComposer.applyCoroutineContext
// 调用remember,创建LaunchedEffectImpl
remember(key1) { LaunchedEffectImpl(applyContext, block) }
}
3、3 LaunchedEffectImpl
LaunchedEffectImpl继承RememberObserver,创建了协程,首次组合,开启协程;离开组合,取消协程。
internal class LaunchedEffectImpl(
private val parentCoroutineContext: CoroutineContext,
private val task: suspend CoroutineScope.() -> Unit,
) : RememberObserver, CoroutineExceptionHandler {
// 创建协程
private val scope =
CoroutineScope(
parentCoroutineContext +
if (parentCoroutineContext[CompositionErrorContextImpl] != null) {
this
} else {
EmptyCoroutineContext
}
)
private var job: Job? = null
/**
* 首次组合开启协程
*/
override fun onRemembered() {
// This should never happen but is left here for safety
job?.cancel("Old job was still running!")
job = scope.launch(block = task)
}
/**
* 组件从树中移除,取消协程
*/
override fun onForgotten() {
job?.cancel(LeftCompositionCancellationException())
job = null
}
/**
* 被抛弃后取消协程
*/
override fun onAbandoned() {
job?.cancel(LeftCompositionCancellationException())
job = null
}
// CoroutineExceptionHandler implementation to save on allocations
override val key: CoroutineContext.Key<*>
get() = CoroutineExceptionHandler.Key
override fun handleException(context: CoroutineContext, exception: Throwable) {
context[CompositionErrorContextImpl]?.apply {
exception.attachComposeStackTrace(this@LaunchedEffectImpl)
}
parentCoroutineContext[CoroutineExceptionHandler]?.handleException(context, exception)
?: throw exception
}
}
LaunchedEffectImpl什么时候被创建?答案就在remember函数
3、4 remember
如果是首次组合或者没有发生改变,就创建LaunchedEffectImpl对象,调用updateRememberedValue将LaunchedEffectImpl对象保存到slots数组。updateRememberedValue调用了updateCachedValue
@Composable
public inline fun <T> remember(
key1: Any?,
crossinline calculation: @DisallowComposableCalls () -> T,
): T {
// currentComposer.changed(key1) key是否发生改变
return currentComposer.cache(currentComposer.changed(key1), calculation)
}
public inline fun <T> Composer.cache(invalid: Boolean, block: @DisallowComposableCalls () -> T): T {
@Suppress("UNCHECKED_CAST")
return rememberedValue().let {
if (invalid || it === Composer.Empty) {
val value = block()
updateRememberedValue(value)
value
} else it
} as T
}
3、5 updateCachedValue
changeListWriter.remember会调用到pushRemember。
// Composer.kt
internal fun updateCachedValue(value: Any?) {
val toStore = if (value is RememberObserver) {
// 首次组合,添加到changeList
if (inserting) { changeListWriter.remember(value) }
// 添加到set
abandonSet.add(value)
// 包装下`LaunchedEffectImpl`对象
RememberObserverHolder(value, rememberObserverAnchor())
} else value
// 保存到slots数组
updateValue(toStore)
}
3、6 pushRemember
添加到operations的Remember里面,在Compose原理三之SlotTable中介绍过operations。
fun pushRemember(value: RememberObserver) {
operations.push(Remember) {
setObject(Remember.Value, value)
}
}
最终LaunchedEffectImpl对象被保存到了slots数组和operations里面。
3、7 什么时候调用RememberObserver的onRemembered?
在应用变更阶段,所有变更应用完成后立即调用 当组合完成后,就需要应用变更,应用变更在Compose原理三之SlotTable中介绍过。
// Composition.kt
private fun applyChangesInLocked(changes: ChangeList) {
rememberManager.prepare(abandonSet, composer.errorContext)
try {
// 第一步:应用所有变更
trace("Compose:applyChanges") {
val applier = pendingPausedComposition?.pausableApplier ?: applier
val rememberManager = pendingPausedComposition?.rememberManager ?: rememberManager
applier.onBeginChanges()
// 应用变更
slotTable.write { slots ->
changes.executeAndFlushAllPendingChanges(
applier, slots, rememberManager, composer.errorContext,
)
}
applier.onEndChanges()
}
// 第二步:派发 onRemembered 回调
// Side effects run after lifecycle observers so that any remembered objects
// that implement RememberObserver receive onRemembered before a side effect
// that captured it and operates on it can run.
rememberManager.dispatchRememberObservers() // ⬅️ 这里调用 onRemembered
// 第三步:执行 SideEffect
rememberManager.dispatchSideEffects()
} finally {
// 第四步:派发 onAbandoned
if (this.lateChanges.isEmpty() && pendingPausedComposition == null) {
rememberManager.dispatchAbandons()
}
rememberManager.clear()
}
}
具体派发逻辑RememberEventDispatcher.kt中:
// RememberEventDispatcher.kt
fun dispatchRememberObservers() {
val abandoning = abandoning ?: return
ignoreLeavingSet = null
// 1️⃣ 先派发 onForgotten(倒序)
if (leaving.isNotEmpty()) {
trace("Compose:onForgotten") {
val releasing = releasing
for (i in leaving.size - 1 downTo 0) { // ⬅️ 倒序遍历
val instance = leaving[i]
withComposeStackTrace(instance) {
if (instance is RememberObserverHolder) {
val wrapped = instance.wrapped
abandoning.remove(wrapped) // 从 abandon 集合移除
wrapped.onForgotten() // ⬅️ 调用 onForgotten
}
if (instance is ComposeNodeLifecycleCallback) {
// node callbacks are in the same queue as forgets to ensure ordering
if (releasing != null && instance in releasing) {
instance.onRelease()
} else {
instance.onDeactivate()
}
}
}
}
}
}
// 2️⃣ 再派发 onRemembered(正序)
if (remembering.isNotEmpty()) {
trace("Compose:onRemembered") {
dispatchRememberList(remembering)
}
}
}
private fun dispatchRememberList(list: MutableVector<RememberObserverHolder>) {
val abandoning = abandoning ?: return
list.forEach { instance ->
val wrapped = instance.wrapped
abandoning.remove(wrapped) // 从 abandon 集合移除
withComposeStackTrace(instance) {
wrapped.onRemembered() // ⬅️ 调用 onRemembered
}
}
}
对象如何被添加到remembering列表?Operation.kt中定义了多种操作:
// Operation.kt - Remember 操作
object Remember : Operation(objects = 1) {
inline val Value
get() = ObjectParameter<RememberObserverHolder>(0)
override fun OperationArgContainer.execute(
applier: Applier<*>,
slots: SlotWriter,
rememberManager: RememberManager,
errorContext: OperationErrorContext?,
) {
rememberManager.remembering(getObject(Value)) // ⬅️ 记录需要派发 onRemembered
}
}
// Operation.kt - AppendValue 操作
object AppendValue : Operation(objects = 2) {
override fun OperationArgContainer.execute(
applier: Applier<*>,
slots: SlotWriter,
rememberManager: RememberManager,
errorContext: OperationErrorContext?,
) {
val anchor = getObject(Anchor)
val value = getObject(Value)
if (value is RememberObserverHolder) {
rememberManager.remembering(value) // ⬅️ 记录需要派发 onRemembered
}
slots.appendSlot(anchor, value)
}
}
3、8 什么时候调用RememberObserver的onForgotten?
当对象从组合中移除时调用,发生在应用变更阶段派发 onRemembered 之前。
3、8、1 组件重组时旧值被替换执行onForgotten
在Operation.kt中的 UpdateValue 操作:
// Operation.kt - UpdateValue 操作
object UpdateValue : Operation(ints = 1, objects = 1) {
inline val Value
get() = ObjectParameter<Any?>(0)
inline val GroupSlotIndex
get() = 0
override fun OperationArgContainer.execute(
applier: Applier<*>,
slots: SlotWriter,
rememberManager: RememberManager,
errorContext: OperationErrorContext?,
) {
val value = getObject(Value)
val groupSlotIndex = getInt(GroupSlotIndex)
if (value is RememberObserverHolder) {
rememberManager.remembering(value) // 新值加入 remembering
}
when (val previous = slots.set(groupSlotIndex, value)) {
is RememberObserverHolder -> {
rememberManager.forgetting(previous) // ⬅️ 旧值加入 leaving
}
is RecomposeScopeImpl -> previous.release()
}
}
}
3、8、2 组件从树中移除执行onForgotten
// Composer.kt
fun SlotWriter.removeCurrentGroup(rememberManager: RememberManager) {
// To ensure this order, we call `enters` as a pre-order traversal
// of the group tree, and then call `leaves` in the inverse order.
forAllDataInRememberOrder(currentGroup) { _, slot ->
// even that in the documentation we claim ComposeNodeLifecycleCallback should be only
// implemented on the nodes we do not really enforce it here as doing so will be expensive.
if (slot is ComposeNodeLifecycleCallback) {
rememberManager.releasing(slot)
}
if (slot is RememberObserverHolder) {
rememberManager.forgetting(slot) // ⬅️ 加入 leaving 列表
}
if (slot is RecomposeScopeImpl) {
slot.release()
}
}
removeGroup()
}
3、8、3 销毁组件树执行onForgotten
// Composition.kt
override fun dispose() {
synchronized(lock) {
if (!disposed) {
disposed = true
composable = {}
// composition because this composition is being discarded. It is important that
// this is done after applying deferred changes above to avoid sending `
// onForgotten` notification to objects that are still part of movable content that
// will be moved to a new location.
val nonEmptySlotTable = slotTable.groupsSize > 0
if (nonEmptySlotTable || abandonSet.isNotEmpty()) {
rememberManager.use(abandonSet, composer.errorContext) {
if (nonEmptySlotTable) {
applier.onBeginChanges()
// 移除整个 SlotTable,触发所有对象的 forgetting
slotTable.write { writer ->
writer.removeCurrentGroup(rememberManager)
}
applier.clear()
applier.onEndChanges()
dispatchRememberObservers() // ⬅️ 派发 onForgotten
}
dispatchAbandons()
}
}
composer.dispose()
}
}
parent.unregisterComposition(this)
}
3、8、4 组件树停用执行onForgotten
// Composition.kt
override fun deactivate() {
synchronized(lock) {
checkPrecondition(pendingPausedComposition == null) {
"Deactivate is not supported while pausable composition is in progress"
}
val nonEmptySlotTable = slotTable.groupsSize > 0
if (nonEmptySlotTable || abandonSet.isNotEmpty()) {
trace("Compose:deactivate") {
rememberManager.use(abandonSet, composer.errorContext) {
if (nonEmptySlotTable) {
applier.onBeginChanges()
slotTable.write { writer ->
writer.deactivateCurrentGroup(rememberManager) // ⬅️ 停用处理
}
applier.onEndChanges()
dispatchRememberObservers() // ⬅️ 派发 onForgotten
}
dispatchAbandons()
}
}
}
// ... 清理资源
}
}
onForgotten采用倒序派发,与onRemembered顺序相反。
// RememberEventDispatcher.kt
// onForgotten 采用倒序派发,与 onRemembered 顺序相反
if (leaving.isNotEmpty()) {
trace("Compose:onForgotten") {
for (i in leaving.size - 1 downTo 0) { // ⬅️ 倒序遍历
val instance = leaving[i]
if (instance is RememberObserverHolder) {
wrapped.onForgotten()
}
}
}
}
3、9 onAbandoned() - 什么时候调用?
第一步:记录到 abandonSet
在Composer.kt中的 updateCachedValue() 方法:
// Composer.kt
@PublishedApi
@OptIn(InternalComposeApi::class)
internal fun updateCachedValue(value: Any?) {
val toStore =
if (value is RememberObserver) {
val holder = RememberObserverHolder(value, rememberObserverAnchor())
if (inserting) {
changeListWriter.remember(holder)
}
abandonSet.add(value) // ⬅️ 所有 RememberObserver 先加入 abandonSet
holder
} else value
updateValue(toStore)
}
第二步:成功时从abandonSet移除
// RememberEventDispatcher.kt
private fun dispatchRememberList(list: MutableVector<RememberObserverHolder>) {
val abandoning = abandoning ?: return
list.forEach { instance ->
val wrapped = instance.wrapped
abandoning.remove(wrapped) // ⬅️ 成功 remembered 时从 abandonSet 移除
withComposeStackTrace(instance) {
wrapped.onRemembered()
}
}
}
第三步:组合失败时派发onAbandoned
// RememberEventDispatcher.kt
fun dispatchAbandons() {
val abandoning = abandoning ?: return
if (abandoning.isNotEmpty()) {
trace("Compose:abandons") {
val iterator = abandoning.iterator()
// remove elements one by one to ensure that abandons will not be dispatched
// second time in case [onAbandoned] throws.
while (iterator.hasNext()) {
val instance = iterator.next()
iterator.remove()
instance.onAbandoned() // ⬅️ 对 abandonSet 中剩余的对象调用 onAbandoned
}
}
}
}
3、9、1 组合过程抛出异常触发onAbandoned
// Composition.kt
private inline fun <T> trackAbandonedValues(block: () -> T): T {
var success = false
return try {
block().also { success = true }
} finally {
if (!success && abandonSet.isNotEmpty()) {
// 组合失败,派发 onAbandoned
rememberManager.use(abandonSet, composer.errorContext) {
dispatchAbandons()
}
}
}
}
3、9、2 主动放弃组合变更触发onAbandoned
// Composition.kt
override fun abandonChanges() {
pendingModifications.set(null)
changes.clear()
lateChanges.clear()
if (abandonSet.isNotEmpty()) {
rememberManager.use(abandonSet, composer.errorContext) {
dispatchAbandons() // ⬅️ 派发 onAbandoned
}
}
}
四、DisposableEffect
DisposableEffect使用场景
4、1 监听器注册/注销,使用DisposableEffect
@Composable
fun LocationUpdates() {
val context = LocalContext.current
val locationManager = remember {
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
}
DisposableEffect(Unit) {
val listener = object : LocationListener {
override fun onLocationChanged(location: Location) {
// 处理位置更新
}
}
// 注册监听器
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
1000L, 10f, listener
)
// 返回清理回调
onDispose {
// 取消注册监听器
locationManager.removeUpdates(listener)
}
}
}
4、2 生命周期观察者,使用DisposableEffect
@Composable
fun LifecycleAwareComponent() {
val lifecycle = LocalLifecycleOwner.current.lifecycle
DisposableEffect(lifecycle) {
val observer = LifecycleEventObserver { _, event ->
when (event) {
Lifecycle.Event.ON_START -> println("Started")
Lifecycle.Event.ON_STOP -> println("Stopped")
else -> {}
}
}
lifecycle.addObserver(observer)
onDispose {
lifecycle.removeObserver(observer)
}
}
}
4、2 资源管理,使用DisposableEffect
@Composable
fun MediaPlayerComponent(videoUrl: String) {
DisposableEffect(videoUrl) {
val player = MediaPlayer().apply {
setDataSource(videoUrl)
prepare()
start()
}
onDispose {
player.stop()
player.release()
}
}
}
4、3 DisposableEffect原理
DisposableEffect继承RememberObserver,进入组合时执行effect,获取清理回调,离开组合时执行清理。
// Effects.kt
@Composable
@NonRestartableComposable
public fun DisposableEffect(
key1: Any?,
effect: DisposableEffectScope.() -> DisposableEffectResult,
) {
remember(key1) { DisposableEffectImpl(effect) }
}
private class DisposableEffectImpl(
private val effect: DisposableEffectScope.() -> DisposableEffectResult
) : RememberObserver {
private var onDispose: DisposableEffectResult? = null
override fun onRemembered() {
// 进入组合时执行 effect,获取清理回调
onDispose = InternalDisposableEffectScope.effect()
}
override fun onForgotten() {
// 离开组合时执行清理
onDispose?.dispose()
onDispose = null
}
override fun onAbandoned() {
// 组合废弃时不需要清理(onRemembered 未被调用)
}
}
public class DisposableEffectScope {
public inline fun onDispose(
crossinline onDisposeEffect: () -> Unit
): DisposableEffectResult =
object : DisposableEffectResult {
override fun dispose() {
onDisposeEffect()
}
}
}
五、rememberCoroutineScope
rememberCoroutineScope会创建协程,是作为LaunchedEffect的补充,LaunchedEffect只能在Compose函数中调用,如果想在点击事件中开启协程,那就不能使用LaunchedEffect,因为点击事件回调不在Compose作用域内。
// ❎错误用法
@Composable
fun SearchBar() {
Button(onClick = {
// 点击事件回调不在Compose作用域内,不能使用LaunchedEffect
LaunchedEffect(Unit) {
}
}
}
// ✅正确用法
@Composable
fun SearchBar() {
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
// 开启协程执行相关操作
}
}
}
原理很简单,RememberedCoroutineScope继承CoroutineScope,离开组合时取消所有协程。
// Effects.kt
@Composable
public inline fun rememberCoroutineScope(
crossinline getContext: @DisallowComposableCalls () -> CoroutineContext = {
EmptyCoroutineContext
}
): CoroutineScope {
val composer = currentComposer
return remember { createCompositionCoroutineScope(getContext(), composer) }
}
internal class RememberedCoroutineScope(
private val parentContext: CoroutineContext,
private val overlayContext: CoroutineContext,
) : CoroutineScope, RememberObserver {
@Volatile
private var _coroutineContext: CoroutineContext? = null
override val coroutineContext: CoroutineContext
get() {
// 懒加载:首次访问时才创建
var localCoroutineContext = _coroutineContext
if (localCoroutineContext == null) {
synchronized(lock) {
localCoroutineContext = _coroutineContext
if (localCoroutineContext == null) {
val childJob = Job(parentContext[Job])
localCoroutineContext = parentContext + childJob + overlayContext
}
_coroutineContext = localCoroutineContext
}
}
return localCoroutineContext!!
}
override fun onForgotten() {
cancelIfCreated() // 离开组合时取消所有协程
}
}
六、rememberUpdatedState
6、1 来个例子
下面的代码为什么总是打印 "First"?首次组合的时候,调用CountdownWithCallback,传递onFinish,LaunchedEffect调用onFinish。重组的时候,调用CountdownWithCallback,传递新的onFinish,由于LaunchedEffect的key没有发生改变,LaunchedEffect只执行一次,调用的还是第一次传递过来的onFinish,总是打印 "First"。
// ❎错误用法
@Composable
fun CountdownWithCallback(onFinish: () -> Unit) {
LaunchedEffect(Unit) { // 只启动一次
delay(5000)
onFinish() // ← 捕获的是首次组合时的 onFinish
}
}
@Composable
fun Parent() {
var message by remember { mutableStateOf("First") }
CountdownWithCallback(
onFinish = { println(message) } // 总是打印 "First"
)
Button(onClick = { message = "Second" }) {
Text("Change Message")
}
}
使用rememberUpdatedState就能解决问题。
6、2 rememberUpdatedState原理
remember { mutableStateOf(newValue) } - 首次创建状态对象
.apply { value = newValue } - 每次重组更新值。
rememberUpdatedState原理超级简单,每次重组的时候,更新新值。
// SnapshotState.kt
@Composable
public fun <T> rememberUpdatedState(newValue: T): State<T> =
remember { mutableStateOf(newValue) }.apply { value = newValue }
首次组合的时候,调用CountdownWithCallback,传递onFinish,LaunchedEffect调用onFinish。重组的时候,调用CountdownWithCallback,传递新的onFinish。虽然LaunchedEffect的key没有发生改变,LaunchedEffect只执行一次,但是rememberUpdatedState把新的onFinish赋值给了currentOnFinish,每次打印的时候,都是调用最新的回调。
// ✅正确用法
@Composable
fun CountdownWithCallback(onFinish: () -> Unit) {
val currentOnFinish by rememberUpdatedState(onFinish)
LaunchedEffect(Unit) {
delay(5000)
currentOnFinish() // ← 调用最新的回调
}
}
@Composable
fun Parent() {
var message by remember { mutableStateOf("First") }
CountdownWithCallback(
onFinish = { println(message) }
)
Button(onClick = { message = "Second" }) {
Text("Change Message")
}
}
七、SideEffect
onRemembered执行完成后,就会执行SideEffect。
// Composition.kt
private fun applyChangesInLocked(changes: ChangeList) {
rememberManager.prepare(abandonSet, composer.errorContext)
try {
// 第一步:应用所有变更
trace("Compose:applyChanges") {
val applier = pendingPausedComposition?.pausableApplier ?: applier
val rememberManager = pendingPausedComposition?.rememberManager ?: rememberManager
applier.onBeginChanges()
// 应用变更
slotTable.write { slots ->
changes.executeAndFlushAllPendingChanges(
applier, slots, rememberManager, composer.errorContext,
)
}
applier.onEndChanges()
}
// 第二步:派发 onRemembered 回调
// Side effects run after lifecycle observers so that any remembered objects
// that implement RememberObserver receive onRemembered before a side effect
// that captured it and operates on it can run.
rememberManager.dispatchRememberObservers() // ⬅️ 这里调用 onRemembered
// 第三步:执行 SideEffect
rememberManager.dispatchSideEffects()
} finally {
// 第四步:派发 onAbandoned
if (this.lateChanges.isEmpty() && pendingPausedComposition == null) {
rememberManager.dispatchAbandons()
}
rememberManager.clear()
}
}
// 执行 SideEffect
fun dispatchSideEffects() {
if (sideEffects.isNotEmpty()) {
trace("Compose:sideeffects") {
sideEffects.forEach { sideEffect -> sideEffect() }
sideEffects.clear()
}
}
}
7、1 使用场景
场景1:同步状态到非Compose对象
@Composable
fun SyncToAnalytics(userId: String) {
// 每次 userId 变化都会执行
SideEffect {
// 同步到 Analytics SDK(非 Compose 管理)
Analytics.setUserId(userId)
}
}
场景2:触发外部API
@Composable
fun UpdateCanvas(color: Color) {
val canvas = remember { CustomCanvas() }
// 每次颜色变化都重绘
SideEffect {
canvas.setBackgroundColor(color)
}
}
八、总结
- 副作用是在应用变更后执行。
LaunchedEffect在首次组合应用变更的时候会创建协程,重组的时候,如果key没有发生变化,就不会创建协程。像初始化等只需执行一次的代码,就需要使用LaunchedEffect。DisposableEffect在组件离开组件树时执行清理。rememberCoroutineScope可以在Compose函数作用域之外创建协程rememberUpdatedState原理每次重组的时候,更新新值。SideEffect可以同步状态到非Compose对象,可以触发外部API