Compose笔记(八十)--rememberUpdatedState

这一节主要了解一下Compose中使用rememberUpdatedState,在Jetpack Compose开发中,它是一个用于处理‌长效副作用‌与‌最新状态值‌之间矛盾的关键API。在不重启副作用的前提下,确保内部逻辑始终能访问到最新的参数或状态值。简单总结如下:

API:

rememberUpdatedState:长期运行的副作用中,始终引用最新的值,同时避免副作用因为该值的变化而重启。

栗子:

Kotlin 复制代码
import android.util.Log
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay

@Composable
fun CountdownDemo() {
    var tipsText by remember { mutableStateOf("初始提示文字") }
    var showCountdown by remember { mutableStateOf(false) }

    Column(
        modifier = Modifier.padding(top = 200.dp),
        verticalArrangement = Arrangement.Center
    ) {
        Box(modifier = Modifier.padding(20.dp)) {
            Button(onClick = { tipsText = "更新后的新提示文案"; showCountdown = true }) {
                Text("修改文字并开启倒计时")
            }
        }

        if (showCountdown) {
            CountdownTipDialog(tips = tipsText)
        }
    }
}

@Composable
fun CountdownTipDialog(tips: String) {

    val latestTips = rememberUpdatedState(tips)
    
    LaunchedEffect(Unit) {
        repeat(3) {
            delay(1000)
            Log.d("Test","倒计时日志:${latestTips.value}")
        }
    }
    Column(modifier = Modifier.padding(start = 50.dp, top = 100.dp)) {
        Text(text = latestTips.value)
    }

}
Kotlin 复制代码
import android.util.Log
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay

data class Goods(
    val id: Int,
    val name: String,
    val price: String
)

@Composable
fun CarouselDemo() {
    
    val goodsData = remember {
        mutableStateListOf(
            Goods(1, "基础耳机", "99"),
            Goods(2, "机械键盘", "199")
        )
    }
   
    var itemClickAction by remember {
        mutableStateOf<(Goods) -> Unit>({
            Log.d("Test","旧逻辑 点击商品:${it.name}")
        })
    }
  
    var closeDialogAction by remember {
        mutableStateOf({
            Log.d("Test","旧逻辑 弹窗关闭,上报埋点旧接口")
        })
    }
    var showCarouselDialog by remember { mutableStateOf(false) }
    Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
        Column(
            modifier = Modifier.padding(20.dp),
            verticalArrangement = Arrangement.spacedBy(10.dp)
        ) {
            Button(onClick = {
                goodsData.clear()
                goodsData.addAll(listOf(
                    Goods(3, "电竞显示器", "1299"),
                    Goods(4, "无线鼠标", "149")
                ))
            }) {
                Text("更新商品列表")
            }
            Button(onClick = {
                itemClickAction = {
                   Log.d("Test","新逻辑 跳转商品详情页:${it.name}")
                }
            }) {
                Text("替换商品点击回调")
            }
            Button(onClick = {
                closeDialogAction = {
                    Log.d("Test","新逻辑 弹窗关闭,上报新版埋点接口")
                }
            }) {
                Text("替换弹窗关闭回调")
            }
            Button(onClick = { showCarouselDialog = true }) {
                Text("打开商品轮播弹窗")
            }
        }
        if (showCarouselDialog) {
            GoodsCarouselDialog(
                goodsList = goodsData,
                onGoodsClick = itemClickAction,
                onDialogClose = {
                    showCarouselDialog = false
                    closeDialogAction()
                }
            )
        }
    }
}

@Composable
fun GoodsCarouselDialog(
    goodsList: List<Goods>,
    onGoodsClick: (Goods) -> Unit,
    onDialogClose: () -> Unit
) {
    
    val latestGoodsList = rememberUpdatedState(goodsList)
    val latestItemClick = rememberUpdatedState(onGoodsClick)
    val latestCloseCallback = rememberUpdatedState(onDialogClose)

    var currentIndex by remember { mutableIntStateOf(0) }
    var autoCloseCountdown by remember { mutableIntStateOf(30) }

    Box(
        modifier = Modifier
            .fillMaxSize()
            .padding(40.dp),
        contentAlignment = Alignment.Center
    ) {
        Card(
            modifier = Modifier.fillMaxWidth(),
            shape = RoundedCornerShape(12.dp)
        ) {
            Column(modifier = Modifier.padding(16.dp)) {
                Row(
                    modifier = Modifier.fillMaxWidth(),
                    horizontalArrangement = Arrangement.SpaceBetween,
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Text("商品轮播(${autoCloseCountdown}s后自动关闭)")
                    Button(onClick = latestCloseCallback.value) {
                        Text("关闭")
                    }
                }
                val currentGoods = latestGoodsList.value.getOrNull(currentIndex)
                currentGoods?.let { goods ->
                    Box(
                        modifier = Modifier
                            .fillMaxWidth()
                            .size(180.dp)
                            .clickable {
                              
                                latestItemClick.value(goods)
                            },
                        contentAlignment = Alignment.Center
                    ) {
                        Column(horizontalAlignment = Alignment.CenterHorizontally) {
                            Text(text = goods.name, style = MaterialTheme.typography.titleMedium)
                            Text(text = "价格:${goods.price}元")
                        }
                    }
                }
            }
        }
    }
    LaunchedEffect(Unit) {
        while (true) {
            delay(2000)
            val list = latestGoodsList.value
            if (list.isNotEmpty()) {
                currentIndex = (currentIndex + 1) % list.size
            }
        }
    }
    LaunchedEffect(Unit) {
        while (autoCloseCountdown > 0) {
            delay(1000)
            autoCloseCountdown--
        }
        latestCloseCallback.value()
    }
    DisposableEffect(Unit) {
        onDispose {
            Log.d("Test","弹窗销毁,终止轮播&倒计时协程,执行最新关闭回调")
            latestCloseCallback.value()
        }
    }
}

注意:

1 不要滥用‌,如果你希望状态变化时‌重启‌副作用,请直接将该状态作为LaunchedEffect的Key,‌不要‌使用rememberUpdatedState。

2 ‌解包State,‌rememberUpdatedState返回的是State<T>。在Lambda或协程中使用时,尽量使用by委托或直接访问 .value,以确保读取到的是最新值。