Post

React Key는 정체성의 문제다

key가 컴포넌트 정체성을 결정하고 index key가 만드는 실무 버그를 정리

React Key는 정체성의 문제다

React에서 key는 “map 돌릴 때 경고 없애는 값”이 아니다. key는 각 항목(컴포넌트 인스턴스)의 정체성(identity) 을 React에 알려주는 식별자다.

React는 리스트를 다시 렌더링할 때, “이 항목이 이전의 그 항목과 같은 존재인가?”를 판단해야 한다. 그 판단 기준이 key다.


key가 필요한 이유

개념

리스트 렌더링은 렌더마다 “새 JSX 배열”이 만들어진다. React는 이전 렌더의 결과와 다음 렌더의 결과를 비교하면서 최소 변경으로 DOM을 업데이트한다. 이때 리스트 항목 간 매칭은 기본적으로 애매하다.

  • 항목이 추가/삭제/정렬되면
  • 단순히 “몇 번째 요소”인지로는 이전 항목과 다음 항목이 같은지 판단할 수 없다

그래서 React는 key를 통해 이런 매칭을 한다.

같은 key = 같은 인스턴스(같은 정체성) 다른 key = 다른 인스턴스(새로 생성/초기화)

왜 중요한가

정체성이 제대로 유지되면:

  • 입력값 같은 로컬 상태(state) 가 올바른 항목에 붙어 있다
  • 컴포넌트의 생명주기(마운트/언마운트)가 의도대로 작동한다
  • 변경이 일어난 항목만 정확히 업데이트된다

key는 “성능” 이전에 정확성(correctness) 문제다.


index를 key로 쓰면 안 되는 이유

개념

key={index}는 “현재 배열에서 몇 번째인가”를 정체성으로 쓰는 것과 같다. 하지만 배열의 인덱스는 데이터 자체의 정체성이 아니라, 현재 배치 순서일 뿐이다.

왜 중요한가

리스트에 다음 중 하나라도 일어나면 index 기반 정체성은 붕괴한다.

  • 중간 삽입
  • 중간 삭제
  • 정렬 변경
  • 필터링으로 항목 수가 바뀜

이때 React는 이렇게 오해한다.

  • “index 0은 예전에도 index 0이었으니 같은 항목이겠지”
  • 실제로는 다른 데이터가 index 0으로 밀려왔는데도

그 결과, 컴포넌트 인스턴스가 잘못된 데이터와 재사용된다.


컴포넌트 재사용과 초기화 문제

개념

React는 key를 기준으로 컴포넌트를 재사용(reuse)할지, 새로 만들지(remount) 결정한다.

  • 재사용: state 유지, 입력값 유지, ref 유지 가능
  • 초기화(리마운트): state 리셋, 입력 초기화, effect 재실행

왜 중요한가

리스트 UI는 보통 항목마다 내부 state를 가진다.

  • <input>의 입력값
  • 체크박스 상태
  • 펼침/접힘 토글
  • 로딩 스피너 표시 여부

key가 안정적이면 이 state는 “해당 항목”에 붙는다. key가 불안정하면 state가 “자리(index)”에 붙어버린다.


key를 잘못 쓰면 발생하는 버그 시나리오 (실무에서 터지는 형태)

시나리오 1: 입력값이 다른 행으로 이동한다 (가장 흔함)

상황

  • 리스트로 사용자 입력 폼을 렌더링
  • 항목 삭제 시, 아래 항목들이 위로 당겨짐

잘못된 구현

1
2
3
{items.map((item, index) => (
  <Row key={index} item={item} />
))}

증상

  • 3번째 행에 입력했는데
  • 2번째 행을 삭제하니
  • 입력값이 2번째 행으로 “이동”해 보인다

이건 사용자가 입력한 값이 이동한 게 아니라, React가 key=2인 컴포넌트를 “그대로 재사용”해서 새 item을 같은 인스턴스에 붙여버린 것이다.

해결

1
2
3
{items.map(item => (
  <Row key={item.id} item={item} />
))}

시나리오 2: 체크박스 상태가 섞인다

상황

  • 체크박스 리스트
  • 필터/정렬이 자주 발생

증상

  • 체크한 항목이 아닌데 체크된 것처럼 보임
  • 필터를 바꾸면 체크 상태가 엉뚱한 항목에 붙음

원인: key=index → 정체성이 “항목”이 아니라 “자리”에 붙음


시나리오 3: useEffect가 의도치 않게 다시 실행되거나, 반대로 실행되지 않는다

상황

  • 각 항목 컴포넌트가 마운트 시 데이터 로드(useEffect) 수행
  • 리스트가 재정렬됨

증상

  • 재정렬만 했는데 API 요청이 다시 나감(리마운트)
  • 혹은 데이터가 바뀌었는데 effect가 기대대로 초기화되지 않음(재사용)

key가 불안정하면 React는 불필요하게 언마운트/마운트를 반복하거나, 반대로 재사용해 버려서 초기화가 안 된다.


좋은 key의 조건

1) 안정적(stable)이어야 한다

렌더링마다 바뀌면 의미가 없다.

  • key={Math.random()}
  • key={Date.now()}
  • key={someDerivedValueThatChanges}

이건 매번 “새 항목”으로 인식시켜 state가 매번 초기화된다.

2) 고유(unique)해야 한다

형제 리스트에서 중복되면 React가 매칭을 망친다.

3) 데이터의 정체성을 대표해야 한다

가장 좋은 key는 보통 데이터의 id다.


“index key가 허용되는” 예외 조건 (현실적으로 필요한 정리)

다음 조건이 모두 만족되면 key=index가 큰 문제가 안 될 수 있다.

  • 리스트가 절대 재정렬/필터/삽입/삭제되지 않는다
  • 항목이 정적이다
  • 항목 컴포넌트가 로컬 state를 가지지 않는다 (입력, 토글 등 없음)

하지만 실무에서 이 조건을 끝까지 지키는 경우는 드물다. 그래서 원칙은 단순하게 가져가야 한다.

리스트가 동적이면 index key 금지


정리: key는 React에게 “이건 누구냐”를 알려주는 값이다

  • key는 성능 팁이 아니라 정체성(identity)
  • index는 정체성이 아니라 자리(position)
  • 잘못된 key는 UI state를 잘못된 항목에 붙게 만든다
  • 결과는 입력값 이동, 체크박스 섞임, effect 리셋 같은 실무 버그로 터진다

This post is licensed under CC BY 4.0 by the author.