Android Compose中使用SideEffect与不使用SideEffect实现BackHandler的区别
以下是两个实现的完整代码参考:
// 实现1:使用SideEffect处理回调状态更新 @Composable fun BackHandler( backDispatcher: OnBackPressedDispatcher, enabled: Boolean = true, // 控制是否需要拦截返回事件 onBack: () -> Unit ) { val backCallback = remember { /* ... */ } // 在每次成功重组时,使用`enabled`参数值更新回调,告知`backCallback`是否需要拦截返回事件 SideEffect { backCallback.isEnabled = enabled } /* 其余代码 */ } // 实现2:直接在Composable函数体中更新回调状态 @Composable fun BackHandler( backDispatcher: OnBackPressedDispatcher, enabled: Boolean = true, // 控制是否需要拦截返回事件 onBack: () -> Unit ) { val backCallback = remember { /* ... */ } backCallback.isEnabled = enabled /* 其余代码 */ }
二者的核心差异主要有三点:
1. 执行时机完全不同
Composable函数体内的代码是在重组运行过程中执行的,而Compose的重组是支持取消、支持中途丢弃的,随时可能因为更高优先级的任务进来中断当前重组。而SideEffect里的代码,只有等这次重组完全跑完、确认所有状态都要生效、不会被废弃了才会执行。
举个很常见的场景:现在正在跑一次低优先级的重组,突然用户点了屏幕触发了更高优先级的重组,之前那次低优先级的重组直接被扔了,如果是直接写在函数体里的赋值,可能已经改了backCallback的开关状态,但这次重组本来就是要作废的,相当于平白无故改了不该改的状态,而SideEffect里的逻辑因为重组没成功,根本不会跑,就不会有这个问题。
2. 状态一致性保障不同
Compose要求Composable本身是无副作用的,你反复跑多少次都不该改外部的东西,不然重组本身可重复的特性就会出问题。
直接在函数体里改backCallback的属性,就是在改外部状态,要是重组跑了一半被取消了,就会出现「回调的开关已经变了,但UI和Compose内部的状态还是旧的」的不一致问题,两边对不上就会出奇怪的bug,比如该拦截返回的时候不拦,不该拦的时候反而拦了。
用SideEffect就不会有这个问题,它只会等重组完全搞定,所有状态都同步好了,才去改回调的开关,保证两边状态永远是对的上的。
3. 可靠性不同
带SideEffect的是官方推荐的标准写法,不管什么场景下表现都是稳定可预期的。
不带SideEffect的写法,你平常正常用可能看不出问题,但一碰到重组取消、列表快速滑动导致频繁重组这种极端场景,就容易出随机bug,属于不符合Compose开发规范的写法。
内容的提问来源于stack exchange,提问作者user1307179

