rememberUpdatedStateの使い方
最終更新日:2024-08-08
LaunchedEffect
や DisposableEffect
で時間のかかる処理をした後、Composableの引数を使った処理をやりたいときがあります。
@Composable
internal fun MyComposable(
done: () -> Unit,
) {
LaunchedEffect(Unit) {
// 3秒待ってから
delay(3000)
// 引数で渡されるdoneを呼ぶ!
done()
}
Text("...")
}
コールバックの done()
が変化するたびにコルーチンを再起動するのは望ましくないので、 LaunchedEffect()
の引数には Unit
を指定しています。
しかし再Composeが走ると done
が変化していることもあります。こんな時に使えるのが rememberUpdatedState
です。
@Composable
internal fun MyComposable(
done: () -> Unit,
) {
val currentDone by rememberUpdatedState(done)
LaunchedEffect(Unit) {
// 3秒待ってから
delay(3000)
// rememberUpdatedStateの中身を呼ぶ!
currentDone()
}
Text("...")
}
これで、時間のかかる処理をした後、再Composeによって最新のものに差し替えられた done()
が呼ばれるようになります。
具体例で確認する
公式ドキュメントを見ても動きをイメージしづらいので、具体例で確認してみます。
@Composable
fun MainScreen() {
var counter by remember { mutableStateOf(0) }
// 結果をいれておく用
var goodDone by remember { mutableStateOf(-1) }
var badDone by remember { mutableStateOf(-1) }
// 今の値をキャプチャー
val v = counter
Column(modifier = Modifier.padding(paddingValues)) {
Bad(
count = counter,
doneValue = badDone,
) {
badDone = v
}
Good(
count = counter,
doneValue = goodDone,
) {
goodDone = v
}
// counterを増やすボタン
Button(onClick = {
++counter
}) {
Text("Add")
}
}
}
Bad
は引数の done()
をそのまま呼ぶ方法にしてみます。
@Composable
fun Bad(
count: Int,
doneValue: Int,
done: () -> Unit,
) {
LaunchedEffect(Unit) {
delay(3000)
// そのまま呼ぶ
done()
}
Text("Count=${count} done=${doneValue}")
}
Good
は rememberUpdatedState
を使ってみます。
@Composable
fun Good(
count: Int,
doneValue: Int,
done: () -> Unit,
) {
val currentDone by rememberUpdatedState(done)
LaunchedEffect(Unit) {
delay(3000)
// rememberUpdatedStateの中身を呼ぶ!
currentDone()
}
Text("Count=${count} done=${doneValue}")
}
実行し、3秒の間にボタンを連打してみます。
Bad
のほうは最初にComposeされた時の done()
を呼んでいるため、 doneValue
は0になりました。 一方 Good
のほうは最後にComposeされたときの done()
が呼ばれ、 doneValue
は9になりました。