React key가 컴포넌트를 구별하는 방법

#에디터#React

React key 왜 꼭 식별해야 할까요?

React Fiber 재조정 과정에서 key는 단순한 최적화가 아닌 컴포넌트의 정확한 식별자 역할을 합니다. key가 중복되면 개발 모드에서 경고가 나타나지만, 더 심각한 문제는 React가 컴포넌트를 잘못 재사용하면서 마운트/언마운트 생명주기가 의도와 다르게 동작한다는 점입니다.

저는 실제로 React key가 제대로 구분되지 않아 컴포넌트가 복제되는 이슈를 경험했습니다. 이 경험을 통해 React key의 중요성과 마운트/언마운트 생명주기가 어떻게 동작하는지 알게 되었습니다.

문제 상황

공동편집 상황에서 3명 이상이 편집할 때 커서가 복제된다는 이슈가 발생했습니다.

공동편집 환경과 전례 없는 컴포넌트 복제 현상이 겹치면서 문제의 원인이 뭔지 추측조차 되지 않았습니다. 특히 3명 이상의 공동편집 상황을 혼자서 재현하기는 어려웠는데요. 컴퓨터 두 대를 사용해서 3개의 웹페이지를 열고, 여러 번의 테스트를 통해 정확한 이슈 시나리오를 특정할 수 있었습니다.

재현 시나리오:

  • 유저A가 관찰하는 화면에서
  • 유저B와 유저C가 같은 텍스트를 편집 중
  • 유저B가 다른 텍스트 컴포넌트로 커서를 이동
  • 결과: 유저A 화면에서 유저B의 커서가 이전 위치와 새 위치에 동시에 표시됨

원인 파악

처음에는 공동편집 환경의 네트워크 동기화 지연이나 상태 업데이트 순서 이슈로 생각했습니다. 그러나 디버깅을 거듭한 끝에, 의외로 단순한 원인을 발견했습니다.

바로 React key 중복이었습니다. 유저 A 화면에서 원격 유저들의 커서 컴포넌트가 모두 remoteTextIdkey로 사용하고 있었는데, 이 때문에 동일한 텍스트를 편집하는 여러 유저들의 커서를 React가 구분하지 못했던 것입니다..

React 재조정 과정에서 key가 중복되면 어떤 컴포넌트를 유지하고 어떤 것을 제거할지 올바르게 결정할 수 없습니다. 그래서 유저 B가 다른 텍스트로 이동했을 때 이전 위치의 커서가 언마운트되지 않고 남은 채, 새 위치에 또 다른 커서가 추가되어 한 유저의 커서가 두 곳에 동시에 표시되는 컴포넌트 복제 현상이 발생한 것입니다.

잘못된 key 예시

{remote.map(p => (
  <RemoteCursor
    key={p.remoteTextId} // text ID만 사용 - 형제 범위에서 중복 가능
  />
))}

React 재조정과 마운트/언마운트 생명주기

React 재조정(Reconciliation) 과정

React는 새로운 렌더링이 발생할 때 이전 Virtual DOM과 새로운 Virtual DOM을 비교해서 실제 DOM에 어떤 변경사항을 적용할지 결정합니다. 이 과정을 재조정이라고 합니다.

재조정은 성능 최적화를 위해 꼭 필요한 DOM 조작만 수행하려고 합니다. 예를 들어 1000개의 리스트 아이템 중 1개만 변경되었다면, 전체를 다시 그리지 않고 해당 1개만 업데이트합니다.

React 컴포넌트 생명주기 요약

단계시점주요 동작
마운트 (Mount)컴포넌트가 처음 생성되어 DOM에 추가될 때인스턴스 생성, DOM 요소 추가, 초기 상태 설정, 이벤트 리스너 등록
업데이트 (Update)props 또는 state가 변경될 때이전 값과 새 값 비교, 변경된 부분만 리렌더링, DOM에 변경사항 반영
언마운트 (Unmount)컴포넌트가 DOM에서 제거될 때cleanup 실행, 이벤트 리스너/타이머/구독 해제, DOM 요소 제거 및 메모리 정리

핵심 포인트

  • 마운트: 컴포넌트의 "탄생" - 필요한 리소스 초기화
  • 업데이트: 컴포넌트의 "변화" - 상태 변경에 따른 적절한 반응
  • 언마운트: 컴포넌트의 "소멸" - 메모리 누수 방지를 위한 정리

React 재조정과 생명주기에서 발생한 컴포넌트 복제 이슈

재조정 관점

React는 Virtual DOM을 비교하면서 key를 기준으로 컴포넌트를 식별합니다. 원격 유저들의 커서 컴포넌트가 모두 동일한 key를 사용한 상태에서 같은 텍스트를 편집했고, 그 결과 하나의 컴포넌트 트리 안에 동일한 key를 가진 컴포넌트가 중복 생성되었습니다.

  • React는 key를 기준으로 "같은 key → 같은 컴포넌트"라고 인식합니다.

  • C가 계속 편집 중이라 동일한 key를 가진 커서 컴포넌트가 살아 있고, 해당 컴포넌트가 계속 업데이트되고 있는 상황이었습니다.

  • 이 때문에 B가 이동할 때 원래라면 B의 커서가 언마운트돼야 했지만, C의 업데이트가 먼저 반영되면서 B의 언마운트가 반영되지 않았습니다.

  • React는 결국 B의 커서를 언마운트하지 않고, 같은 key를 가진 컴포넌트로 업데이트 처리를 해버렸습니다.

  • 동시에 새 텍스트에는 또 다른 B의 커서가 새로 마운트되면서, 결과적으로 B의 커서가 두 곳에 동시에 존재하게 되었습니다.

이슈 상황

텍스트1: B 이동 → (B 언마운트 예정) → C 편집 → C 업데이트 & B 언마운트 건너뜀 & B 업데이트
텍스트2: B 마운트

정상 동작

텍스트1: B 이동 → B 언마운트 → C 편집 → C 업데이트
텍스트2: B 마운트

결과적으로 B 커서가 두 텍스트에 동시에 존재하는 복제 현상이 발생했습니다.

생명주기 관점

정상적으로라면 커서 이동 시 다음과 같은 흐름이 필요합니다.

  • 이전 블록: RemoteCursor(B)언마운트 (useEffect cleanup)

  • 새 블록: RemoteCursor(B)마운트 (useEffect)

정상 로그 예시

[UNMOUNT] B, alpha
[MOUNT]   B, beta

하지만 실제 버그 상황에서는 이렇게 됐습니다.

  • 이전 블록: RemoteCursor(B)RemoteCursor(C)가 동일 key

  • C가 계속 편집 중 → React는 이 key를 "이미 있는 컴포넌트"로 판단

  • B 언마운트 대신 업데이트 처리

  • 새 블록에는 또 다른 B가 마운트

버그 로그 예시

[MOUNT]   B, beta
[UNMOUNT] B, alpha 없음

즉, B가 이동하면서 언마운트가 호출되기 전에 업데이트로 대체되어 언마운트가 건너뛰어진 것이 문제였습니다.

  • key는 단순히 최적화를 위한 값이 아니라 컴포넌트 식별자다.

  • 특히 공동편집처럼 여러 사용자가 동시에 같은 블록을 편집할 수 있는 경우, sessionId 등 고유한 값을 반드시 key로 사용해야 한다.

  • 그렇지 않으면 React가 재조정 과정에서 업데이트로 잘못 처리 → 언마운트가 건너뛰어짐 → 복제 현상 같은 버그가 발생할 수 있다.

OT와 sessionId, 그리고 해결

OT 기반 협업에서의 세션

공동편집은 보통 OT(Operational Transformation) 알고리즘을 기반으로 구현합니다.

  • 사용자의 편집 연산을 순서·시간과 무관하게 수용할 수 있도록 변환(Transform)합니다.

  • 이때 각 사용자를 구분하기 위해 sessionId라는 고유한 값을 부여합니다.

즉, OT 레벨에서는 "누가 어떤 연산을 했는가"를 sessionId로 식별합니다.

UI에서도 필요한 sessionId

커서 표시 역시 "누가 어디를 편집 중인가"를 보여주는 UI입니다. 하지만 기존 코드에서는 key를 sessionId 없이 remoteTextId만 사용했습니다. 결과적으로 B와 C가 같은 블록에 있으면 key 충돌이 발생했고, React는 커서를 제대로 구분하지 못했습니다.

sessionId로 해결

해결책은 간단했습니다. OT에서 이미 존재하는 sessionId를 key 설계에 반영하는 것이었습니다. sessionId 을 사용하면 형제 범위에서 항상 유일성이 보장됩니다.

// 수정된 코드
{remote.map(p => (
  <RemoteCursor
    key={p.sessionId}
  />
))}

react key를 식별하는 이유는...

이 이슈는 UI에서 커서가 "복제된 것처럼 보인다"는 단순한 현상으로 시작했지만, 근본 원인은 React key 설계에 있었습니다.

처음에는 네트워크 동기화 문제나 상태 업데이트 순서를 의심했지만, 원인은 의외로 기본적인 것이었습니다. 바로 "동일 key → 잘못된 재조정 → 언마운트 누락" 이라는 단순한 원칙을 놓친 것이죠.

key는 단순한 성능 최적화 수단이 아니라 컴포넌트의 정체성을 결정하는 장치입니다. 경고가 보이지 않는다고 해서 안전한 것이 아니며, key 충돌은 예측 불가능한 방식으로 컴포넌트의 정체성을 무너뜨릴 수 있습니다.