Blog
리액트에서 key를 사용하는 이유
서론#
React에서 리스트를 렌더링할 때 map을 쓰면
거의 항상 key 경고를 만나게 된다.
처음에는
"경고를 없애기 위한 값"처럼 보이기도 한다.
하지만 key는
단순한 옵션이 아니라
React가 리스트를 안정적으로 업데이트하기 위해 반드시 필요한 기준이다.
map을 사용할 때 왜 key가 필수인가#
React는 렌더링할 때
"이전 화면"과 "새 화면"을 비교해서
변경된 부분만 업데이트한다.
이 비교 과정에서
리스트의 각 항목이 어떤 항목이었던 건지를 구분할 기준이 필요하다.
그 기준이 key다.
즉 key는
"이 요소는 이전 렌더링의 어떤 요소와 같은 것인가?"
를 React에게 알려주는 값이다.
React의 렌더링 방식과 key의 역할#
리스트가 업데이트될 때는 보통 이런 변화가 생긴다.
- 항목이 추가됨
- 항목이 삭제됨
- 항목의 순서가 바뀜
- 항목 내용만 바뀜
React는 이 변화들을 처리하면서
가능하면 기존 DOM을 재사용하려고 한다.
key가 안정적이면
- 같은 key는 같은 항목으로 인식하고
- 필요한 부분만 업데이트한다
반대로 key가 없거나 불안정하면
- 항목의 정체성이 흔들리고
- 예상과 다른 재사용이 일어난다
결과적으로
UI가 “맞는 것처럼 보이지만”
상태나 입력값이 꼬이는 문제가 생길 수 있다.
index를 key로 사용하면 안 되는 이유#
index는 가장 쉬운 key다.
하지만 index는
리스트가 변할 때 항목의 정체성을 보장하지 못한다.
예를 들어
- 0번, 1번, 2번 항목이 있던 리스트에서
- 맨 앞에 항목이 하나 추가되면
기존 항목들은 그대로인데
index는 전부 1씩 밀린다.
React 입장에서는
- "0번이던 요소가 1번이 됐네"
- "그럼 1번 DOM을 재사용하면 되겠네"
같은 식의 판단을 하게 된다.
이 과정에서 문제가 생긴다.
- 입력 컴포넌트의 값이 엉뚱한 항목으로 이동
- 토글 상태가 다른 항목에 붙어버림
- 애니메이션이 이상한 요소에 적용
- 포커스가 튀거나 유지되지 않음
즉, index key 문제는
렌더링이 틀린 게 아니라
렌더링의 정체성이 어긋나는 문제다.
그럼 언제 index key가 괜찮은가#
완전히 금지 수준은 아니다.
index를 써도 크게 문제가 없는 경우는 보통 이 조건이다.
- 리스트의 순서가 절대 바뀌지 않음
- 항목의 추가/삭제가 없음
- 항목 내부에 상태를 가진 입력/토글 같은 요소가 없음
즉, 정적인 목록일 때만 예외적으로 허용 가능하다.
하지만 실제 서비스에서는
이 조건을 만족하는 경우가 많지 않다.
해결 방법#
핵심은 단순하다.
key는 "항목의 고유 식별자"여야 한다.
그래서 해결 방법은 보통 아래 중 하나로 해결한다.
서버에서 내려오는 고유 id 사용 (권장)#
가장 이상적이다.
- post.id
- user.id
- comment.id
같이 데이터가 가진 정체성을 그대로 쓰면 된다.
클라이언트에서 안정적인 id를 만들기 (차선)#
서버 id가 없거나, 임시 데이터라면
생성된 id가 렌더링마다 바뀌지 않게 관리해야 한다.
- 데이터 생성 시점에 id를 부여하고 유지
- 임시 목록이라면 uuid를 만들어 저장
포인트는 하나다.
렌더링마다 새로 생성한 값은 key로 쓰면 안 된다.
(예: Math.random(), 렌더링 중 uuid 생성)
불가피하면 “안정적인 조합 키” 사용 (최후)#
정말 id가 없다면
충돌 가능성이 낮은 조합으로 만든다.
- type + createdAt
- category + name
다만 이 방식은
데이터가 바뀌는 순간 key가 바뀔 수 있어서
최후의 선택에 가깝다.
정리#
key는
React가 리스트를 그릴 수 있게 만드는 값이 아니다.
React가 리스트를 일관되게 업데이트할 수 있게 만드는 값이다.
그래서 key는
"대충 넣는 값"이 아니라
항목의 정체성을 설명하는 중복되지 않는 값이어야 한다.
관련 게시글
8개
JSX가 무엇인지 정리하기
JSX가 무엇인지 개념부터 정리합니다. HTML처럼 보이지만 JavaScript 문법 확장이라는 점과, 빌드 과정에서 함수 호출로 변환되는 구조를 중심으로 설명합니다.
메모이제이션이란 무엇인가
메모이제이션이 무엇인지 개념부터 정리합니다. 계산 최적화 기법으로서의 메모이제이션이 어떤 전제에서 성립하는지, 그리고 언제 의미를 가지는지를 중심으로 설명합니다
자주 사용하는 리액트 훅 (2)
React에서 구조와 렌더링 타이밍을 다루기 시작하면서 사용하게 된 훅들을 정리합니다. useLayoutEffect, useContext, 커스텀 훅이 어떤 상황에서 유용했는지를 중심으로 기록합니다.
자주 사용하는 리액트 훅 (1)
React에서 가장 자주 사용하는 훅인 useState, useEffect, useRef를 정리합니다. 각 훅이 무엇을 위한 도구인지와 어떤 상황에서 유용한지를 중심으로 설명합니다.
props drilling이 생겼던 구조와 당시 선택
React에서 상태를 상위로 끌어올리면서 발생한 props drilling 구조를 정리합니다. 중간 컴포넌트가 props를 전달만 하게 된 문제와, 그 시점에서 Context API를 선택지로 고려하게 된 이유를 기록합니다.
useState로 상태를 관리하다가 불편해지기 시작한 지점
React에서 useState로 상태를 관리하던 중, 페이지 이동·새로고침·뒤로가기 상황에서 상태가 유지되지 않았던 문제를 정리합니다.
clsx 다음에 tw-merge가 필요한 이유
Tailwind CSS에서 clsx로 조건부 클래스를 합친 뒤, tw-merge로 클래스 충돌을 정리하는 이유를 정리합니다. clsx와 tw-merge를 함께 사용하는 흐름과 cn 유틸 패턴을 설명합니다.
clsx / classnames를 언제, 왜 사용하는가
Tailwind CSS를 사용하며 조건부 클래스가 늘어날 때, clsx와 classnames를 언제, 왜 사용하게 되는지 정리합니다. 문자열 기반 클래스 처리의 한계와 정리 기준을 학습 관점에서 설명합니다.