You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Compose uiState提升异常:嵌套调用无法获取最新状态值

问题原因分析

两种写法的核心差异在于闭包捕获的uiState是否为可观察的State对象:

  1. 不拆分的写法:
    uiState是通过by viewModel.uiState.collectAsState()得到的委托属性,本质是State<TestUiState>。当在onLoadMore闭包中访问uiState.isFirstFetching时,每次都会读取State.value,自然能拿到最新的状态值。

  2. 拆分后的写法:
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 08:07:44