1.LaunchedEffect
Composable组件初始化时执行一次。在可组合项内运行挂起函数,如果依赖的Key发生变化(如果前一次协程正在工作将会提前结束)重新运行挂起函数。传入1~N个KEY
var a by remember { mutableStateOf(0) }
LaunchedEffect(a) {
println("a changed $a")
}
Button(onClick = {
a++
}) {
Text("增加")
}
2.produceState:将非Compose状态转换为Compose状态
在协程中将任意值从非Compose状态转为Compose状态
可以传入key值(支持1~3个或vararg动态参数),在key变化状态时重新启动producer。也可以不传,在compose中只运行一次。
fun <T> produceState(
initialValue: T,
producer: suspend ProduceStateScope<T>.() -> Unit
): State<T>
fun <T> produceState(
initialValue: T,
key1: Any?,
producer: suspend ProduceStateScope<T>.() -> Unit
): State<T>
下方代码中,ApiClient是retrofit HTTP库,执行网络请求。
val warehouseResponse : State<List<GetAllWarehouseResponseItem>?> = produceState<List<GetAllWarehouseResponseItem>?>(null) {
val result = ApiClient.apiService.getAllWarehouse()
value = result.data //result.data的类型是List<GetAllWarehouseResponseItem>?
}
Column {
warehouseResponse.value?.forEach { item ->
Text(item.name)
}
}
在输入内容时,重新发送网络请求。
var s by remember { mutableStateOf("") }
val warehouseResponse : State<List<GetAllWarehouseResponseItem>?> = produceState<List<GetAllWarehouseResponseItem>?>(
null,
s /* 变化时重新运行lambda */
) {
val result = ApiClient.apiService.getAllWarehouse(s)
value = result.data //result.data的类型是List<GetAllWarehouseResponseItem>?
}
Column {
warehouseResponse.value?.forEach { item ->
Text(item.name)
}
TextField(value = s, placeholder = {
Text("请输入搜索内容")
}, onValueChange = { s = it })
}
3.derivedStateOf
根据一个或多个状态派生出新的只读状态。当状态与页面的更新频率不需要一致时,使用这个API
官方文档的示例:
@Composable
// When the messages parameter changes, the MessageList
// composable recomposes. derivedStateOf does not
// affect this recomposition.
fun MessageList(messages: List<Message>) {
Box {
val listState = rememberLazyListState()
LazyColumn(state = listState) {
// ...
}
// Show the button if the first visible item is past
// the first item. We use a remembered derived state to
// minimize unnecessary compositions
val showButton by remember {
derivedStateOf {
listState.firstVisibleItemIndex > 0
}
}
AnimatedVisibility(visible = showButton) {
ScrollToTopButton()
}
}
}
listState.firstVisibleItemIndex本就是可以让UI重组的状态,但如果不用derivedStateOf,则每容器的滚动条每滚动一点都会触发一次AnimatedVisibility元素的重组,性能不佳
官方文档的错误示例:
// DO NOT USE. Incorrect usage of derivedStateOf.
var firstName by remember { mutableStateOf("") }
var lastName by remember { mutableStateOf("") }
val fullNameBad by remember { derivedStateOf { "$firstName $lastName" } } // This is bad!!!
val fullNameCorrect = "$firstName $lastName" // This is correct
4.snapshotFlow
块内的state变化时会收集变化的新值,如果与上一次发出的值不相等,则会发出新的值,相同的值不会触发收集(collect)
个人理解:监听复杂的状态(如List中的某个成员的某个state)变化,而执行一个事件
以下代码实现的效果是需要移除元素时,(假设items每个元素对应一个组件)只需将对应索引的visible.targetState设为false,就会在动画结束后移除(而不是立即 移除,这样就没有动画效果,很突兀)
val turquoiseColors =
listOf(
Color(0xff07688C),
Color(0xff1986AF),
Color(0xff50B6CD),
Color(0xffBCF8FF),
Color(0xff8AEAE9),
Color(0xff46CECA)
)
class ColoredItem(val visible: MutableTransitionState<Boolean>, val itemId: Int) {
val color: Color
get() = turquoiseColors.let { it[itemId % it.size] }
}
val items: MutableList<ColoredItem> = remember { mutableStateListOf() }
Column {
LaunchedEffect(Unit) {
snapshotFlow {
//任意一个成员的visible改变且对应的元素动画已经结束了就会
items.firstOrNull { it.visible.isIdle && !it.visible.targetState }
}
.collect {
if (it != null) {
// 存在不可见且动画结束的元素,可以在此执行删除操作。不关注抛出来的it,而是使用items.removeAll+items.filter去移除已经消失且动画结束的所有元素,因为可能在短时间内被移除了多个
}
}
}
}
snapshotFlow {}
.onStart {
//事件:开始收集
}
.onCompletion {
//事件:停止收集
}
.collect {
}
5.SideEffect
在可组合函数发生重组后(完成后)执行一次。官方文档说:
使用
SideEffect
可确保在每次成功重组后执行该效果。另一方面,在保证成功重新组合之前执行效果是不正确的,直接在可组合项中编写效果就是这种情况。
重点是在重组后,并且只执行一次。下面代码的println("外侧代码")可能会在重组时意外的运行多次,它不可控、不可靠。(虽然我在运行下面这段代码时是符合我的预期的......)
GPT4的回答:
SiteEffect
用来处理这样的副作用。你可以将它用于在 Compose 中执行一次性的操作,比如进行数据请求、更新全局状态、执行动画、日志记录、订阅事件等。它的行为和LaunchedEffect
类似,但是它有自己的特性和适用场景。
我还不知道它具体有什么用,可能需要写到具体业务逻辑才会明白......
var s by remember { mutableStateOf(0) }
SideEffect {
println("siteEffect $s")
}
println("outside $s")
Column {
Button(onClick = {
s++
}) {
Text("plus")
}
Text("s: $s")
}
点击多次后的日志:(好像没啥意外的....)
2024-11-30 19:27:15.086 15327-15327 System.out com.dmjyb.cangku I outside 0
2024-11-30 19:27:15.106 15327-15327 System.out com.dmjyb.cangku I siteEffect 0
2024-11-30 19:27:18.622 15327-15327 System.out com.dmjyb.cangku I outside 1
2024-11-30 19:27:18.623 15327-15327 System.out com.dmjyb.cangku I siteEffect 1
2024-11-30 19:27:20.240 15327-15327 System.out com.dmjyb.cangku I outside 2
2024-11-30 19:27:20.241 15327-15327 System.out com.dmjyb.cangku I siteEffect 2
2024-11-30 19:27:21.557 15327-15327 System.out com.dmjyb.cangku I outside 3
2024-11-30 19:27:21.559 15327-15327 System.out com.dmjyb.cangku I siteEffect 3
2024-11-30 19:27:23.256 15327-15327 System.out com.dmjyb.cangku I outside 4
2024-11-30 19:27:23.257 15327-15327 System.out com.dmjyb.cangku I siteEffect 4
6.rememberUpdatedState
适用于在保证可组合函数内的LaunchedEffect(或其它DisposableEffect等)访问状态变量的参数是最新的值
也就是通过参数传递过来的状态,保证其在Effect APIs中访问得到的是最新的值,而不是旧的值。
下面的代码在ChildScreen中如果不用rememberUpdatedState
那么在点击Increment counter和赋值新的lambda按纽后,访问的依然是旧的值和旧的函数引用
@Composable
fun ParentScreen() {
var counter by remember { mutableStateOf(0) }
var onTimeout by remember { mutableStateOf({
println("Parent Timeout triggered with counter: $counter")
}) }
println("parent recomposition")
Column {
Button(onClick = { counter++ }) {
Text("Increment counter")
}
Text("parent val: $counter")
Button(onClick = {
onTimeout = {
println("new lambda")
}
}) {
Text("赋值新的lambda")
}
ChildScreen(counter, onTimeout = onTimeout)
}
}
@Composable
fun ChildScreen(i:Int, onTimeout: () -> Unit) {
val currentOnTimeout by rememberUpdatedState(onTimeout)
val currentInt by rememberUpdatedState(i)
LaunchedEffect(true) {
while (true) {
delay(2000)
currentInt
currentOnTimeout()
}
}
}
7.DisposableEffect
对于需要在键发生变化或可组合项退出组合后进行清理的附带效应
var a by remember { mutableStateOf(0) }
DisposableEffect(a) {
println("DisposableEffect")
onDispose {
println("onDispose")
}
}
println("recomposition")
Column {
Button(onClick = {
a++
}) {
Text("增长")
}
Text("a: $a")
}
以上代码执行日志:
recomposition
DisposableEffect
a++
recomposition
onDispose
DisposableEffect
a++
recomposition
onDispose
DisposableEffect
第一次进入组件时,会执行DisposableEffect但不会执行onDispose;每次a变化时,都会重新启动DisposableEffect{},但是会先执行onDispose 用于清理上一次初始化/监听的一些资源;在组件退出时会执行onDispose
也可以不监听key值,只用于初始化和退出时销毁
DisposableEffect(Unit) {
println("启动的时候执行")
onDispose {
println("退出的时候销毁启动时打开的资源")
}
}
8.rememberCoroutineScope
在composable中启动协程,去处理动画、延时、网络请求等耗时的工作
val scope = rememberCoroutineScope()
val a = remember { Animatable(0f) }
Button(onClick = {
scope.launch {
delay(2000)
a.animateTo(1f)
}
}) {
Text("Button")
}