如何让Composable函数内的逻辑代码仅执行一次?
问题原因
Jetpack Compose的Composable函数支持重组,只要监听的状态发生变化,就会触发关联Composable树的重新执行,你的写法存在两个触发循环执行的问题:
preloadViewModel.getSyncData()直接写在Composable根层级,每次重组都会重新调用接口,接口返回更新uiStateSync状态后再次触发重组,形成死循环。- 导航逻辑直接写在
when分支中,当uiState.state为SUCCESS时,每次重组都会调用一次navigate方法,导致重复导航。
解决方案
使用Compose官方提供的LaunchedEffect副作用函数包裹需要限定执行次数的逻辑,LaunchedEffect仅会在传入的key发生变化时重新执行内部代码。
- 页面首次进入才需要执行的初始化请求,用
LaunchedEffect(Unit)包裹,key为Unit时内部逻辑仅会在Composable首次加载时执行一次。 - 导航逻辑用
LaunchedEffect(uiState.state)包裹,仅当请求状态发生变化时才会检查是否需要触发导航,避免重复调用导航方法。
修改后完整代码示例
@Composable fun PreloadView(navHostController: NavHostController) { val preloadViewModel: PreloadViewModel = viewModel() val syncState by preloadViewModel.uiStateSync.observeAsState() // 首次进入页面仅执行一次数据请求 LaunchedEffect(Unit) { preloadViewModel.getSyncData() } syncState?.let { PreloadContent(navHostController = navHostController, it) } } @Composable fun PreloadContent(navHostController: NavHostController, uiState: UiState<SyncResponse>) { // 仅当请求状态变更为SUCCESS时触发一次导航 LaunchedEffect(uiState.state) { if (uiState.state == RequestState.SUCCESS) { navHostController.navigate(KYCScreen.StartKYC.name) } } when (uiState.state) { RequestState.FAIL -> { Box( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxHeight() .fillMaxWidth() ) { Text(text = "Error", color = Color.Black) } } RequestState.NON, RequestState.SUCCESS -> { // 加载中/导航前的占位UI Box( contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize() ) { CircularProgressIndicator() } } } }
注意事项
如果你的PreloadViewModel作用域绑定的是上层导航图而非当前PreloadView,页面销毁后ViewModel不会被回收,再次进入页面需要重新拉取数据的话,可以将LaunchedEffect的key替换为唯一的页面标识,比如当前路由地址即可。
内容的提问来源于stack exchange,提问作者AnTran
相关产品推荐
相关产品推荐

