rememberUpdatedState 이해하기

Jetpack Compose를 사용하다 보면 상태(State)와 재구성(Recomposition)에 대해 자주 마주하게 됩니다. rememberUpdatedState는 이러한 상태 관리에서 중요한 역할을 합니다. 공식 문서에 나와 있는 설명을 보면 다음과 같습니다.

reference a value in an effect that shouldn’t restart if the value changes

저는 이 설명이 처음에 잘 이해되지 않았습니다. 그래서 rememberUpdatedState를 이해하기 위해 정리 겸 이 글을 쓰게 되었습니다. 이 글이 다른 사람에게 도움이 되기를 바랍니다.

예시 코드

다음은 rememberUpdatedState를 설명하기 위해 공식 문서에서 사용된 샘플 코드입니다. LandingScreen 컴포저블은 최초 생성 후 일정 시간(SplashWaitTimeMillis)이 지나면 onTimeout 함수를 호출하는 역할을 합니다.

중요한 요구사항은 LandingScreen이 재구성 되더라도 딜레이는 늘어나면 안 된다는 점입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Composable
fun LandingScreen(onTimeout: () -> Unit) {

// This will always refer to the latest onTimeout function that
// LandingScreen was recomposed with
val currentOnTimeout by rememberUpdatedState(onTimeout)

// Create an effect that matches the lifecycle of LandingScreen.
// If LandingScreen recomposes, the delay shouldn't start again.
LaunchedEffect(true) {
delay(SplashWaitTimeMillis)
currentOnTimeout()
}

/* Landing screen content */
}

위 코드에서 rememberUpdatedState의 역할은 무엇일까요? rememberUpdatedState를 사용하지 않고 onTimeoutLaunchedEffect에서 사용하면 어떻게 될까요?

rememberUpdatedState 없이 사용하는 경우

LaunchedEffect는 키가 true 로 설정되어 한번만 실행됩니다. 하지만 LandingScreenonTimeout 이 변경될 때마다 재구성됩니다.

이때 LaunchedEffect는 처음 실행될 때 캡쳐한 onTimeout을 사용하기 때문에, LandingScreenonTimeout이 변경되어 재구성된 후에도 처음 받은 onTimeout을 호출하게 됩니다.

1
2
3
4
5
6
7
8
9
@Composable
fun LandingScreen(onTimeout: () -> Unit) {
LaunchedEffect(true) {
delay(SplashWaitTimeMillis)
onTimeout() // 변경된 onTimeout이 아니라 처음 캡처된 onTimeout이 호출됨
}

/* Landing screen content */
}

LaunchedEffect의 키로 onTimeout을 사용하는 경우

LaunchedEffect의 키로 onTimeout을 사용하면, onTimeout이 변경될 때마다 LaunchedEffect가 재실행됩니다.

이렇게 하면 항상 최신의 onTimeout이 호출되지만, LaunchedEffect가 재실행될 때마다 delay(SplashWaitTimeMillis)도 다시 시작되므로 딜레이 시간이 길어질 수 있습니다.

1
2
3
4
5
6
7
8
9
@Composable
fun LandingScreen(onTimeout: () -> Unit) {
LaunchedEffect(onTimeout) {
delay(SplashWaitTimeMillis)
onTimeout() // 항상 최신 onTimeout이 호출됨
}

/* Landing screen content */
}

rememberUpdatedState의 역할

rememberUpdatedState를 사용하면 LaunchedEffect가 재실행되지 않으면서도 항상 최신의 onTimeout을 참조할 수 있습니다. 이는 LaunchedEffect가 재실행되지 않아 딜레이 시간이 유지되면서도, 최신 상태를 유지할 수 있게 합니다.

이제 아래의 rememberUpdatedState에 대한 공식 문서의 한줄 설명이 이해가 되시나요?

reference a value in an effect that shouldn’t restart if the value changes

혹시 이 글이 다소 이해가 되지 않는다면 람다와 클로저, 그리고 이펙트에 대해 다시 학습해보면 도움이 되리라 생각합니다.

구현 보기

마지막으로 rememberUpdatedState의 구현을 코드로 살펴봅시다.

rememberUpdatedState는 아래와 같이 구현되어 있습니다. remember에서 mutableStateOf(newValue)를 통해 newValue를 값으로 갖는 State를 반환 합니다. 따라서 이렇게 반환된 State는 리컴포지션이 발생하더라도 변경되지 않고 캐싱됩니다.

1
2
3
fun <T> rememberUpdatedState(newValue: T): State<T> = remember {
mutableStateOf(newValue)
}.apply { value = newValue }

그리고 바로 apply { value = newValue }를 통해 상태를 최신 값인 newValue로 업데이트 합니다. 즉 이 구현은 상태 객체 자체는 유지하면서 값만 최신으로 업데이트하는 역할을 합니다. 이를 통해 최신 값을 항상 참조할 수 있습니다.