기능 구현

명령형 Cesium API를 React 선언형 모델에 통합하기

한 줄 요약

Cesium의 마우스 이벤트 핸들러를 React 컴포넌트 안에 등록했는데, 컴포넌트가 언마운트되어도 이벤트가 계속 발생했다. 메모리 누수와 의도하지 않은 동작이 반복됐다. 세 가지 패턴 — 컴포넌트 라이프사이클 바인딩, Hook-per-Entity 추상화, 커스텀 이벤트 버스 — 으로 해결했고, 이 패턴 적용 후 페이지 전환 시 이전 페이지의 이벤트 잔류 문제가 완전히 제거됐다.

선언형과 명령형의 충돌

React는 선언형: "무엇이 보여야 하는가"를 기술하면 React가 DOM을 관리한다.

Cesium의 ScreenSpaceEventHandler는 명령형: 이벤트를 직접 등록하고, 사용이 끝나면 직접 해제해야 한다. 해제를 빠뜨리면 이벤트 핸들러가 잔류하여 메모리 누수와 이벤트 충돌을 일으킨다.

패턴 1: 컴포넌트 마운트/언마운트 = 이벤트 등록/해제

각 도구를 독립된 React 컴포넌트로 구현하여, 컴포넌트의 마운트/언마운트가 곧 이벤트의 등록/해제가 되도록 설계했다.

"거리 측정" 도중에 "면적 측정"을 누르면: 거리 컴포넌트 언마운트(이벤트 해제 + 임시 엔티티 정리) → 면적 컴포넌트 마운트(이벤트 등록). 이벤트를 언제 등록/해제할 것인가라는 명령형 문제를 어떤 컴포넌트가 화면에 있는가라는 선언형 문제로 변환한 것이다.

패턴 2: Hook-per-Entity 추상화

3D 공간의 동적 엔티티(점, 선, 라벨)를 각각 독립된 커스텀 훅으로 캡슐화했다. 도구 컴포넌트는 Cesium Entity API를 직접 호출하지 않는다:

도구 컴포넌트 (DistanceTool, AreaTool, ...)
  └── 커스텀 훅 조합
        ├── useDynamicPoint     → 커서 따라다니는 점
        ├── useDynamicLine      → 실시간 미리보기 선
        └── useMeasureLines     → 확정된 측정선 목록

새 도구 추가 시 기존 훅을 조합하기만 하면 된다. "수직·수평 거리" 도구는 동적 선 훅 3개(대각선, 수직, 수평)를 조합하여 구현했다.

패턴 3: 커스텀 이벤트 버스

Profile(단면도) 도구에서 메인 뷰어와 별도 뷰어 간 실시간 동기화가 필요했다. React의 상태 트리만으로는 처리가 어려워, 싱글톤 이벤트 매니저(Pub/Sub)로 해결했다. 발행자와 구독자가 서로를 몰라도 된다.

이 렌더링 성능은 react-compiler-optimization 노드에서 다루는 React Compiler로 추가 최적화됐다.

이 경험에서 추출한 원칙

  1. 명령형 API를 선언형 모델 안에 흡수하라. 이것은 Cesium뿐 아니라 D3, Canvas, WebGL 같은 명령형 라이브러리를 React에서 사용할 때 일반적으로 적용 가능한 패턴이다.

  2. 추상화의 단위는 "도구"가 아니라 "엔티티"다. 엔티티(점, 선, 라벨) 단위로 추상화하면 도구는 "훅의 조합"이 되어 일관성과 재사용성을 동시에 얻는다.

  3. React 상태 관리의 한계를 인식하고, 보완적 시스템을 설계하라. 3D 렌더링 엔진의 이벤트는 UI 계층을 따르지 않으므로 Pub/Sub 같은 별도 통신 채널이 필요할 수 있다.

Cesium(또는 Three.js, D3)을 React에서 사용하고 있다면, 명령형 리소스의 생성/해제가 컴포넌트 라이프사이클에 바인딩되어 있는지 확인하라. useEffect cleanup에서 해제되지 않는 리소스가 있다면 메모리 누수 대상이다.