Skip to content

by_remember의_리컴포지션

임형준 edited this page Nov 18, 2024 · 1 revision

💥 상태를 by remember를 통해 호이스팅하여 관리했을 때 리컴포지션 문제 발생

문제 정의

Github Compose-samples | Jetsnack | Cart.kt

Compose-samples를 보며 얻은 인사이트 중 하나는 상태 호이스팅을 통해 상위 컴포저블에서 하위 컴포저블의 상태를 by remember 키워드를 사용해 관리하고, 하위 컴포저블에는 상태의 실제 값만 전달하는 방식이었다.


하지만 이 방식에서 한 가지 문제가 있었다.


by remember 키워드로 상태를 관리하는 상위 컴포저블의 상태가 변경되면 리컴포지션이 발생하면서, 이 상태를 전달받는 하위 컴포저블도 불필요하게 전부 리컴포지션된다는 점이다.

image

이는 PR에 질문을 남겨주신 팀원분의 질문에 답하기 위하여 실제 동작을 확인하면서 문제가 발생하고 있다는 것을 인지했다.


문제 상황을 뒤늦게 발견한 이유는 여러가지가 있었다.

  1. Compose-samples를 충분히 검증하지 않고 방법을 참고한 원인
  2. 공식문서의 코드도 위와 같은 방식을 사용
  3. 공식문서에서 =by 는 동일한 선언이라는 말에 너무 쉽게 결정한 원인

즉, 충분히 분석하고 내가 직접 확인하여 사용한 것이 아니라 단순히 공식문서라는 이유로 그 방법이 정답이라 생각했던 것이 이 문제가 발생한 가장 근본적인 원인이다.


위 문제를 확인하고 Layout Inspector를 통해 리컴포지션 횟수를 두 눈으로 확인했고, 아래와 같이 TextField를 수정했을 때, 해당 화면에서 제일 상위 컴포저블인 SavePhotoScreen이 리컴포지션이 일어나는 것을 확인했다.

image


이 부분에 대해서는 너무 화가나서 Compose-Samples는 리컴포지션이 어떻게 일어나고 있나 Clone하여 직접 실행시켜봤는데,,, 위 이미지와 똑같은 문제가 발생하고 있는 상태였다…


즉, 잘못된 방식을 보고 따라해버린 것이다…!


문제 해결 방법

by remember 에서 = remember 로 바꾸었고, 하위 컴포저블에게 데이터를 전달하는 방식을 값 자체를 전달하는 방식에서 State<T> 를 통해 값을 가지고 있는 상태를 건네주는 것으로 변경하여 해결했다.


문제 해결 진행

팀원의 리뷰에서 State를 넘겨주는 것과 값 자체를 넘겨주는 것에 차이를 두 눈으로 확인했을 때 State를 넘겨줬을 때는 SavePhotoScreen이 변경되지 않았다.

리컴포지션은 직접 그 상태를 .value 를 통해 접근한 부분에서만 리컴포지션이 일어났다.


기존에는 by remember 를 사용했기에 값 자체에 접근했고, 그 값 자체를 하위 컴포저블의 타입 String, Boolean, UiState 등으로 전달해주었다.

하지만 = remember 키워드를 통해 .value 에 접근하지 않고 하위 컴포저블에게는 아래 이미지 처럼 State 자체를 넘겨주는 방식으로 변경하니 리컴포지션이 일어나지 않았다.

image


❓왜 이런 결과가 발생했을까??

우선 by remember 코드를 살펴보자.


by 키워드를 사용한다면, 바로 값에 접근할 수 있다.

어떻게 접근하는 건지 내부 코드를 타고 들어간다면 확인할 수 있다.

image

즉 값을 가져올 땐 State.getValue 를 통해서 가져오고 값을 설정할 땐 MutableState.setValue 를 통해 값을 설정하는 것이다.


이제 = remember 의 동작을 확인해보자.

image

= remember { mutableStateOf() }MutableState 자체에 접근한다.

= remember 키워드를 사용할 땐 value 필드에 접근해서 값을 가져와야 한다.


val rememberDescription = remember { mutableStateOf(description) }

위의 코드에서 rememberDescription 의 값에 직접 접근하려면 rememberDescription.value 를 통해 접근해야 하는 것이다.


❓ 그럼 MutableState 에서 State 를 오버라이드한 value 는 어떤 동작으로 값을 가져오는 걸까?

image

by 위임과 같은 동작을 하는 State.getValue 를 통해 실제 값을 가져오는 것이다.

여기서 중요한 부분이 아래의 설명이다.


“A mutable value holder where reads to the value property during the execution of a Composable function, the current RecomposeScope will be subscribed to changes of that value.”

컴포저블 함수가 실행되는 동안 .value(getValue) 를 읽으면 현재 실행중인 컴포저블은 해당 상태의 변경사항을 구독(subscribed)한다.


When the value property is written to and changed, a recomposition of any subscribed RecomposeScopes will be scheduled.

value 속성이 작성되고 변경되면 해당 상태를 구독한 컴포지션은 리컴포지션의 대상이된다.


위에 설명에서 알 수 있다시피 .value 를 통해 State.value 를 통해 상태를 구독한다면, 해당 상태가 변경될 때 리컴포지션이 된다는 것이다.


💡 즉, 이전의 구현에서 by remember 를 사용했을 때, by 델리게이트에 의해 값에 접근한 순간 State.value 에 의해 구독상태가 된 것이고, 해당 값이 변경됨으로써 리컴포지션이 일어났던 것이다.

Clone this wiki locally