React Native의 작동 원리

구글링하면 많이 나오는 내용이지만 정리할 목적과 나중에 빠르게 다시 볼 수 있도록 하기 위한 목적으로 기록한다.

 

React Native는 4가지 스레드를 갖는다.

 

그러나 가장 핵심적인 부분들로 구분하면 다음과 같이 구분할 수 있다.

1. UI 스레드 (메인 스레드)

2. JS 스레드

3. Native Bridge

 

UI 스레드

UI 스레드는 메인 스레드라고 하기도 한다.

이름에서 알 수 있듯 UI를 그리는 스레드로 안드로이드, IOS의 UI를 그리는 역할을 맡는다.

또한 UI 이벤트가 발생하는 내역을 JS 스레드로 넘기는 역할도 수행한다.

 

JS 스레드

마찬가지로 이름에서 알 수 있듯 JS 코드를 처리하는 스레드이다.

위에서 적었듯, UI 스레드에서 넘어온 이벤트를 바탕으로 JS 코드를 실행하거나 API 호출 등 각종 비즈니스 로직 등을 처리한다.

네이티브 뷰 업데이트에 관한 내용은 일괄적으로 처리되어 이벤트 루프의 끝에서 UI 스레드로 전달된다.

 

Native Bridge

UI 스레드와 JS 스레드가 통신할 수 있도록 하는 중개자 역할을 수행한다.

Native Bridge에서 병목이 발생하면 React Native 앱의 성능 저하가 발생한다.

 

 

실행 과정

  1. 앱을 처음 시작하면 UI 스레드가 실행된다.
  2. 실행된 UI 스레드는 JS 스레드를 실행시켜 자바스크립트 번들을 로딩한다.
  3. 자바스크립트 코드가 모두 로드되면 UI 스레드는 그 코드들을 모두 JS 스레드로 보낸다. 그래야 무거운 JS 코드가 돌아가더라도 UI 스레드는 Load가 없어 문제가 생기지 않기 때문이다.
    (아마 내부적인 로직에 이상이 생기거나 속도가 느려지는 것은 에러를 던져서 사용자에게 알리거나 사용자가 좀 기다리게 하면 되지만 UI를 그리는 부분이 문제가 생기면 사용자에게 어떠한 대처를 하기 어렵기 때문에 그런 것이 아닐까 싶다.)
  4. 이제 JS 스레드가 실행되어 자바스크립트 번들을 로드하면 리액트는 가상 DOM을 생성하고 Diffing 알고리즘으로 변경 사항을 탐색한다.
  5. 탐색 완료된 변경 사항은 Native Bridge를 통해 Shadow 스레드로 전달된다.
  6. Shadow 스레드는 전달 받은 변경 사항 데이터로 레이아웃을 계산하고 계산 완료된 레이아웃의 파라미터나 객체를 UI 스레드로 보낸다.
  7. UI 스레드가 전달 받은 레이아웃 데이터를 바탕으로 UI를 렌더링한다.
  8. 사용자가 화면에서 UI 이벤트를 발생시키면 이벤트 정보들이 Native Bridge를 경유하여 JS 스레드로 넘겨진다.
  9. UI 이벤트 정보를 바탕으로 JS스레드에서 비즈니스 로직들이 실행되고 리액트는 다시 가상 DOM을 생성하여 변경 사항을 탐색한다.
  10. 4번에서 9번까지의 과정이 앱이 종료될 때까지 계속 반복된다.

 

참고 1) Shadow 스레드

위에서 언급된 Shadow 스레드는 Native Bridge를 통해 넘어온 가상 DOM의 변경 사항을 바탕으로 레이아웃을 계산하고, 계산한 레이아웃 파라미터나 객체를 UI 스레드로 전달한다.

화면에 렌더링하는 것은 UI 스레드만 가능하므로 Shadow 스레드가 계산한 레이아웃 데이터를 UI 스레드에 전달하면 UI 스레드가 해당 레이아웃을 화면에 렌더링한다.

 

참고 2) 데드 라인

IOS는 초당 60프레임, 안드로이드는 초당 90~120프레임을 표시한다. 따라서 IOS, 안드로이드 각각 16.67ms (1/60초), 8.33 ~ 11.11ms(1/120~1/90초) 안에 JS 스레드에서 이벤트 루프를 완료한 후 UI 스레드로 전달하고 UI를 그려서 한 프레임을 렌더링해야 프레임 끊김 없이 화면을 볼 수 있다. 실제로 프로젝트를 개발한 후, 특히 안드로이드에서 프레임이 조금 끊기는 것을 느꼈는데 아마 코드를 비효율적으로 작성하는 등 최적화된 상태가 아니어서 그러한 현상이 발생했던 것 같다.

하지만 아직은 이 수치를 봐도 어떻게 코드를 짜야 데드 라인 내로 렌더링이 완료되도록 할 수 있을지 감이 잘 오지 않는다.

 

ScrollView와 navigatorIOS는 온전히 UI 스레드에서 실행되기 때문에 Native Bridge에서 병목 현상을 발생시키지 않는다.

 

참고 3) Diffing 알고리즘

리액트는 가상 DOM을 활용해 변경된 부분을 비교/탐색하고 변경된 곳만 부분적으로 리렌더링하는 방식을 사용한다.

 

좀 더 구체적으로 설명하면 컴포넌트 내에서 상태값이 변경되면 해당 컴포넌트를 dirty하다고 표시하고 batch에 추가한다.

그 후 가상 DOM의 요소와 실제 DOM의 요소를 비교 순회하면서 dirty 체크가 되어있는 컴포넌트들을 처리한다. 처리하는 과정에서 속성값만 변한 경우에는 속성값만 업데이트하고 해당 컴포넌트의 태그나 컴포넌트 자체가 변경된 경우라면 해당 노드를 포함한 하위 모든 노드를 제거하고 새로운 가상 DOM으로 대체한다.

 

아무튼 이때 가상 DOM에서 변경된 부분을 탐색하는 알고리즘이 바로 Diffing 알고리즘이다.

간단하게 설명하면 트리 구조 상에서 같은 레벨끼리만 비교하여 변경된 부분을 탐색하는 방식이다.

어플리케이션에서는 컴포넌트 간 계층의 변화가 거의 없기 때문에 가능한 방식이다.

 

+ Recent posts