Compose uiState提升异常:嵌套调用无法获取最新状态值
两种写法的核心差异在于闭包捕获的uiState是否为可观察的State对象:
不拆分的写法:
uiState是通过by viewModel.uiState.collectAsState()得到的委托属性,本质是State<TestUiState>。当在onLoadMore闭包中访问uiState.isFirstFetching时,每次都会读取State.value,自然能拿到最新的状态值。拆分后的写法:
uiState是作为普通参数传入的TestUiState对象,而非State。当下层TestScreen重组时,虽然传入了新的uiState,但OnBottomReached中的LaunchedEffect仅以lazyListState为key,而lazyListState是通过remember保存的不变对象,因此LaunchedEffect不会重启,里面的onLoadMore闭包仍持有第一次传入时的旧uiState引用,导致永远读取初始值。
提供两种常用的解决方式:
方式一:使用rememberUpdatedState捕获最新的uiState
在下层TestScreen中,用rememberUpdatedState包裹传入的uiState,确保闭包每次访问的都是最新值:
@Composable internal fun TestScreen( uiState: TestUiState, ) { val listState = rememberLazyListState() // 记住最新的uiState引用 val latestUiState = rememberUpdatedState(uiState) listState.OnBottomReached { // 通过latestUiState.value访问最新值 if (latestUiState.value.isFirstFetching) return@OnBottomReached loadMore() } }
这种方式无需修改OnBottomReached的实现,适合快速解决问题。
方式二:让LaunchedEffect感知uiState变化
修改OnBottomReached函数,添加依赖key参数,将uiState作为key传入,确保uiState变化时LaunchedEffect重启,更新闭包引用:
第一步:修改OnBottomReached实现
@Composable fun LazyListState.OnBottomReached( buffer: Int = 0, // 添加依赖key,用于感知状态变化 vararg keys: Any?, onLoadMore: (() -> Unit) ) { require(buffer >= 0) { "buffer cannot be negative, but was $buffer" } val lazyListState = this // 将lazyListState和传入的keys都作为LaunchedEffect的触发条件 LaunchedEffect(lazyListState, *keys) { snapshotFlow { lazyListState.layoutInfo.visibleItemsInfo } .collect { visibleItems -> val lastVisibleItemIndex = visibleItems.lastOrNull()?.index ?: -1 val shouldLoadMore = lastVisibleItemIndex == lazyListState.layoutInfo.totalItemsCount - 1 - buffer if (shouldLoadMore) { onLoadMore() } } } }
第二步:调用时传入uiState作为key
@Composable internal fun TestScreen( uiState: TestUiState, ) { val listState = rememberLazyListState() listState.OnBottomReached(keys = arrayOf(uiState)) { if (uiState.isFirstFetching) return@OnBottomReached loadMore() } }
这种方式更灵活,可同时传入多个依赖状态,确保闭包始终持有最新的参数引用。
内容的提问来源于stack exchange,提问作者roger chang

