[React] 동시성(Concurrent) 모드 그리고 Recoil

한국인개발자
6 min readJun 15, 2021

--

최종 수정일: 2021. 06. 15

동시성 모드 소개

결론부터 말하자면 React 개발팀은 꾸준히동시성 모드를 준비하고 있으며, 향후 React 진영에서 새로운 방식의 UI 구현 매커니즘을 제시하려 하는 듯 하다(Facebook 팀 내부적으로는 이미 사용하는 듯 하다).

[참고] (eng) The Plan for React 18

[참고] (kor) React Concurrent모드 소개

React 공식 문서의 동시성 모드에 대한 짧은 설명은 다음과 같다.

Concurrent모드는 React 앱이 빠른 반응속도를 유지하도록 하고 사용자의 장치 기능 및 네트워크 속도에 적절하게 맞추도록 돕는 새로운 기능들의 집합체입니다.

위에 첨부한 문서를 읽으면 더 자세한 설명을 살펴볼 수 있다. 당장은 SSR 지원 등의 이슈가 있어 실험적 단계이지만, React 18 혹은 그 후에 동시성 모드가 지원될 시대를 위해 새로운 UI 매커니즘을 준비하도록 하자.

차단과 인터럽트

기본적으로 서비스를 운영할 때 서버 등의 환경을 통해 데이터가 전달될 때 데이터가 준비되지 않았다면 2가지 선택을 할 수 있다(기본적으로 UI 라이브러리는 둘 중 하나를 선택해 제공한다).

  1. 차단
  2. 인터럽트

차단의 경우는 업데이트 렌더링(새로운 DOM 노드 생성 및 컴포넌트 내에 있는 코드 실행하는 것을 포함해서)을 시작하면 DeadLocking과 유사하게 렌더링에 가해지는 모든 작업을 차단하는 것을 의미한다.

인터럽트의 경우는 차단과 달리 렌더링 작업 중간에 끼어들어 일련의 작업이 가능하게 하도록 하는 방식을 의미한다.

차단과 인터럽트의 차이를 아래의 예시를 통해 본격적으로 살펴보자.

보편적으로 UI 라이브러리들은 ‘차단'을 지원하기 때문에 스크롤링, 타이핑 등의 호출이 빈번한 이벤트가 서버와 통신하는 경우 해당 이벤트가 포함된 컴포넌트에 대해 렌더링 주기를 조절할 필요가 있다.

  • 렌더링 1 -> 렌더링 2 -> 렌더링 3 -> … -> 최종 렌더링

그렇지 않으면 UI에 흔히 ‘버벅거림'이 동반되게 되는데, 그 이유는 한 번 렌더링이 시작되면 중간에 그만둘 수 없어 최종 렌더링까지의 모든 렌더링이 선형으로 처리되기 때문에 소프트웨어 성능이 낮아지기 때문이다.

물론 이를 해결하기 위한 방법으로 디바운싱스로틀링을 통해 소프트웨어의 손상을 줄일 수 있다. 하지만, 두 방식 역시 저전력 장치에서는 버벅거림이 동반되기 때문에 최적의 UX를 제공하기는 어렵다.

디바운싱은 동일한 이벤트를 일정 주기마다 모아서 한 번에 처리하는 등 기법이고, 스로틀링은 이벤트에 딜레이를 주는 등의 기법이다.

디바운싱은 Grouping, 스로틀링은 Filtering을 통해 특정 이벤트에 제한을 두는 기법이다.

동시성 모드는 렌더링을 인터럽트 가능하도록 만들어 근본적인 문제를 해결해 결론적으로 최적의 UX를 제공하도록 한다.

렌더링 과정이 중첩되어 다음과 같은 상황을 병렬적으로 처리할 수 있어 렌더링을 중간에 종료하거나 예외 상황이 발생하면 이전 렌더링을 불러오는 등의 작업이 가능해진다.

  • 렌더링 1
  • 렌더링 2
  • 렌더링 3
  • 최종 렌더링

Recoil

Facebook의 실험적 라이브러리인 Recoil은 React답게(Reactish) 상태를 관리하는 라이브러리이다.

기존의 Flux기반의 상태관리 도구가 가졌던 문제를 해결하기 위해 ReactEurope 2020 에서 Dave McCabe에 의해 소개되었다.

[참고] (eng) Recoil: State Management for Today’s React — Dave McCabe aka @mcc_abe at @ReactEurope 2020

[참고] (eng) Recoil Motivation

[참고] (kor) Recoil Motivation

우선 Recoil 공식 문서에 의하면 기존 Flux기반 상태관리 도구들이 가졌던 문제는 다음과 같다.

  • 컴포넌트의 상태는 공통된 상위요소까지 끌어올림으로써 공유될 수 있지만, 이 과정에서 거대한 트리가 다시 렌더링되는 효과를 야기하기도 한다.
  • Context는 단일 값만 저장할 수 있으며, 자체 소비자(consumer)를 가지는 여러 값들의 집합을 담을 수는 없다.
  • 이 두가지 특성이 트리의 최상단(state가 존재하는 곳)부터 트리의 잎(state가 사용되는 곳)까지의 코드 분할을 어렵게한다.

위 문제는 중앙집중형 Store로 상태를 관리함으로 야기되는 문제라고도 할 수 있다. 그래서 Recoil은 근본적인 문제를 해결하기 위해 중앙관리가 아닌 Atom이라는 단위에서 특정 상태를 관리하는 분산관리 형태로 상태를 관리해 필요한 상태를 해당 컴포넌트에 hooks를 통해 직접 주입시키는 방식을 체택했다.

https://morioh.com/p/3682bf6e369a

중앙집중형에 대한 대안으로 React의 Context API를 떠올릴 수도 있지만, 위에서도 언급한 것처럼 Context API는 양이 일정한 컴포넌트에 대한 상태를 관리하기는 용이할지 모르나 양이 가변적인 컴포넌트에 대한 상태를 관리하기는 불가능하기 때문에 한계가 분명하다.

이외에 Recoil과 관련된 다양한 자료들에서 기존 상태관리 라이브러리와 비교를 하므로 여기서는 Recoil을 소개하기보다는 동시성이라는 주제를 바탕으로 Recoil에 접근해보려고 한다.

동시성 측면에서 기존의 상태관리 라이브러리들은 하나의 상태가 변화할 때마다 관리되는 모든 상태를 가져와야 한다는 단점이 존재한다(기존의 상태관리 라이브러리들은 컴포넌트와 상태가 직교하는 형태로 관리하면 되지 않기 때문이다).

만약 기존 상태관리 라이브러리를 사용해 상태 개별적으로 관리하는 것이 가능하다고 생각된다면 Recoil 문서를 살펴보고 비교해보자.

기존 라이브러리를 사용한다면 이를 최대한 개선하기 위해 코드를 분할해 업데이트되는 상태만 따로 빼서 관리할 수도 있지만, 그러자면 보일러플레이트가 커지고 관리해야 할 리소스가 상태에 따라 O(n)으로 증가한다.

하지만 Recoil에서는 컴포넌트와 상태가 직교하기 때문에 O(1)로 상태를 관리할 수 있게된다.

따라서 Recoil을 통해 상태를 관리하게 되면 React를 사용해 최적의 상태관리를 할 수 있게 된다.

이 글에서는 코드가 아닌 줄글로 설명했지만, Facebook은 동시성(Concurrent) 모드와 Recoil을 통해 새로운 UI 환경을 구상하고 있으니 관심이 있다면 해당 문서를 찾아보면 도움이 될 것이다.

--

--