반응형

React로 리스트를 렌더링하다 보면 한 번쯤 이런 경고를 만난다.

각 리스트 항목에는 고유한 key가 필요합니다.

처음에는 단순히 경고를 없애기 위해 key를 넣는 경우가 많다. 하지만 React의 key는 경고 제거용 속성이 아니다. 리스트 안에서 “이 항목이 어떤 데이터인지”를 React에게 알려주는 식별자다.

특히 입력창, 체크박스, 포커스, 애니메이션, 내부 state가 있는 리스트에서는 key 선택이 실제 버그로 이어질 수 있다.

React key는 항목의 정체성을 알려준다

React는 상태가 바뀔 때마다 JSX 결과를 다시 만들고, 이전 렌더 결과와 다음 렌더 결과를 비교한다. 이 과정을 통해 어떤 부분을 유지하고 어떤 부분을 바꿀지 판단한다.

문제는 리스트다. 리스트에는 같은 컴포넌트가 여러 번 반복된다. React 입장에서는 이전 렌더의 첫 번째 항목이 다음 렌더의 어떤 항목과 이어지는지 알아야 한다. 이때 사용하는 값이 바로 key다.

예를 들어 회원 목록을 렌더링한다고 해보자.

members.map((member) => (
  <MemberItem key={member.id} member={member} />
));

여기서 member.id는 항목의 위치가 아니라 데이터 자체를 대표한다. 순서가 바뀌거나 앞에 새 항목이 추가되어도 같은 회원은 같은 id를 가진다.

그래서 React는 “이 컴포넌트는 이전의 그 회원과 같은 항목이구나”라고 판단할 수 있다.

index를 key로 쓰면 왜 위험할까?

가장 흔한 실수는 배열 index를 key로 쓰는 것이다.

members.map((member, index) => (
  <MemberItem key={index} member={member} />
));

겉보기에는 잘 동작하는 것처럼 보인다. 하지만 index는 항목의 정체성이 아니라 현재 위치를 의미한다.

리스트 맨 앞에 새 항목이 추가되면 어떻게 될까?

기존 첫 번째 항목은 두 번째로 밀리고, 두 번째 항목은 세 번째로 밀린다. 데이터의 위치가 전부 바뀐다. 하지만 React는 여전히 key={0}, key={1}, key={2}를 기준으로 컴포넌트를 비교한다.

즉, React는 같은 key를 가진 컴포넌트를 같은 항목으로 취급하지만, 실제 데이터는 다른 항목으로 바뀐 상태가 된다. 이 차이 때문에 문제가 생긴다.

실제로 어떤 버그가 생길 수 있을까?

단순 텍스트 목록에서는 문제가 잘 드러나지 않을 수 있다. 하지만 각 항목 안에 input이 있으면 이야기가 달라진다.

예를 들어 왼쪽 리스트는 key={member.id}를 쓰고, 오른쪽 리스트는 key={index}를 쓴다고 해보자. 각 항목의 input에 값을 입력한 뒤 “맨 앞에 추가” 버튼을 누르면 index key 쪽에서는 입력값이 기대와 다르게 다른 항목에 붙을 수 있다.

왜냐하면 React가 DOM과 컴포넌트 state를 key 기준으로 재사용하기 때문이다.

key={index}를 쓰면 React는 “0번 key를 가진 컴포넌트는 계속 같은 항목”이라고 판단한다. 하지만 리스트 앞에 새 데이터가 들어오면 0번 위치의 실제 데이터는 바뀐다. 그 결과 input 값, focus, 내부 state가 엉뚱한 데이터에 남을 수 있다.

이런 버그는 화면만 봐서는 원인을 찾기 어렵다. 데이터는 맞는데 UI 상태만 이상하게 보이기 때문이다.

index key를 써도 되는 경우

그렇다고 index key가 항상 절대 금지인 것은 아니다. 다음 조건을 모두 만족한다면 사용할 수 있다.

  • 리스트가 정적이다.
  • 항목의 추가, 삭제, 정렬, 필터링이 없다.
  • 항목 내부에 독립적인 state나 input 상태가 없다.
  • 사용할 수 있는 안정적인 id가 정말 없다.

하지만 실무에서는 처음에는 정적인 목록처럼 보여도 나중에 정렬, 필터, 삽입 기능이 추가되는 일이 많다. 그래서 가능하면 처음부터 안정적인 값을 key로 쓰는 편이 좋다.

대표적으로 다음 값들이 적합하다.

  • 서버에서 내려주는 id
  • 데이터베이스 primary key
  • URL에 쓰이는 slug
  • 항목의 생명주기와 함께 유지되는 고유 값

중요한 기준은 “렌더링 순서가 바뀌어도 같은 데이터라면 같은 key를 유지하는가?”이다.

실무에서는 key를 identity 설계로 봐야 한다

시니어 개발자는 key를 단순한 React 문법으로 보지 않는다. 리스트 UI의 identity를 설계하는 문제로 본다.

특히 다음 기능이 들어간 리스트라면 key 안정성을 더 신중하게 봐야 한다.

  • 입력 폼이 있는 리스트
  • 체크박스 선택 상태가 있는 리스트
  • 드래그 앤 드롭 정렬
  • 필터링과 검색
  • 애니메이션
  • 낙관적 업데이트
  • 실시간으로 추가되는 데이터

이런 UI에서 key가 불안정하면 버그가 간헐적으로 나타난다. 사용자는 “입력한 값이 다른 항목으로 이동했다”, “선택 상태가 이상하다”, “포커스가 엉뚱한 곳으로 갔다”고 느낀다.

코드 리뷰에서 key={index}를 발견했다면 바로 삭제하라는 뜻은 아니다. 먼저 이 리스트가 정말 정적인지 확인해야 한다. 앞으로 정렬, 필터, 삽입 가능성이 있는지도 함께 봐야 한다.

결론: key는 위치가 아니라 데이터여야 한다

React의 key는 리스트 항목을 구분하기 위한 식별자다. 단순히 warning을 없애기 위한 값이 아니다.

index는 현재 위치를 의미한다. 반면 좋은 key는 데이터의 정체성을 의미한다.

리스트가 바뀌어도 같은 항목을 같은 항목으로 유지하려면 id, slug, primary key처럼 안정적인 값을 사용해야 한다.

오늘의 핵심은 이 문장으로 정리할 수 있다.

key는 React에게 “이 항목은 위치가 아니라 이 데이터다”라고 알려주는 식별자다.

반응형