key(react)

React가 리스트의 각 요소를 고유하게 식별하기 위해 사용하는 특수한 문자열 속성으로, Virtual DOM 비교(Reconciliation) 시 어떤 요소가 변경·추가·삭제되었는지 판단하는 기준입니다.

key가 없으면 어떻게 되는가?

// key 없이 렌더링
{items.map(item => <li>{item}</li>)}

React는 콘솔에 아래 경고를 출력합니다:

Warning: Each child in a list should have a unique "key" prop.

key가 없으면 React는 순서(index)를 기준으로 비교하므로, index를 key로 사용하는 것과 동일한 문제가 발생합니다.

key의 동작 원리

Reconciliation (재조정)

React는 상태가 변경되면 새 Virtual DOM을 만들고, 이전 Virtual DOM과 비교(Diffing)하여 실제 DOM에 최소한의 변경만 적용합니다. 이때 리스트 요소는 key를 기준으로 매칭합니다.

[이전 VDOM]              [새 VDOM]                [React의 판단]

key="a" → <li>사과       key="c" → <li>포도       key="c": 기존 요소 이동
key="b" → <li>바나나      key="a" → <li>사과       key="a": 기존 요소 이동
key="c" → <li>포도       key="b" → <li>바나나      key="b": 기존 요소 이동

→ 순서만 바뀜, 내부 상태 유지, DOM 노드 재사용

key에 따른 React의 처리

key 상태React의 동작
동일한 key 존재같은 요소로 인식 → 컴포넌트 재사용, 내부 상태 유지
새로운 key 등장새 요소로 인식 → 컴포넌트 마운트 (mount)
기존 key 사라짐삭제된 요소로 인식 → 컴포넌트 언마운트 (unmount)

key의 규칙

1. 형제(sibling) 사이에서 고유해야 한다

// O 같은 리스트 내에서 고유
<ul>
  <li key="a">사과</li>
  <li key="b">바나나</li>
</ul>

// O 다른 리스트 간에는 동일해도 상관없음
<ul>
  <li key="a">사과</li>    {/* 이 "a"와 */}
</ul>
<ol>
  <li key="a">딸기</li>    {/* 이 "a"는 서로 다른 리스트이므로 OK */}
</ol>

// X 같은 리스트 내에서 중복
<ul>
  <li key="a">사과</li>
  <li key="a">바나나</li>  {/* 중복 → 예측 불가능한 동작 */}
</ul>

2. 안정적이어야 한다 (렌더링마다 변하면 안 됨)

// X 매 렌더링마다 새 key → 매번 언마운트/마운트 반복
{items.map(item => <li key={Math.random()}>{item}</li>)}

// X 동일한 문제
{items.map(item => <li key={crypto.randomUUID()}>{item}</li>)}

// O 항상 동일한 값
{items.map(item => <li key={item.id}>{item.name}</li>)}

3. 예측 가능해야 한다

같은 데이터에 대해 항상 같은 key가 부여되어야 합니다.

// O 데이터 생성 시점에 ID 부여
const addTodo = (text) => {
  const newTodo = {
    id: crypto.randomUUID(),  // 생성 시 한 번만 부여
    text,
  };
  setTodos(prev => [...prev, newTodo]);
};

// 렌더링 시에는 이미 부여된 ID 사용
{todos.map(todo => <TodoItem key={todo.id} todo={todo} />)}

key의 활용: 컴포넌트 초기화

key가 변경되면 React는 해당 컴포넌트를 완전히 새로운 요소로 인식하여 언마운트 후 다시 마운트합니다. 이를 활용하면 컴포넌트 상태를 강제로 초기화할 수 있습니다.

function App() {
  const [selectedUserId, setSelectedUserId] = useState(1);

  return (
    // selectedUserId가 바뀌면 ProfileForm이 완전히 새로 마운트됨
    // → 내부 상태(input 값 등)가 자동으로 초기화
    <ProfileForm key={selectedUserId} userId={selectedUserId} />
  );
}

function ProfileForm({ userId }) {
  const [name, setName] = useState('');  // key 변경 시 '' 으로 초기화됨

  return <input value={name} onChange={e => setName(e.target.value)} />;
}
userId=1 선택 → key=1 → ProfileForm 마운트 (name='')
사용자가 "홍길동" 입력 → name='홍길동'
userId=2 선택 → key=2 → 기존 ProfileForm 언마운트 → 새 ProfileForm 마운트 (name='')
                         O 이전 사용자의 입력값이 깔끔하게 초기화됨

정리

항목설명
역할리스트 요소의 고유 식별자, Reconciliation의 매칭 기준
적용 범위형제 요소 사이에서만 고유하면 됨
권장 값서버 ID, DB PK, 생성 시점의 UUID
피해야 할 값index (리스트 변경 시), Math.random() (매번 변경)
활용key 변경으로 컴포넌트 상태 강제 초기화 가능

실무 팁: key는 React의 성능 최적화와 정확한 상태 관리를 위한 핵심 메커니즘입니다. 단순히 경고를 없애기 위해 index를 넣는 것이 아니라, 데이터와 1:1로 매핑되는 안정적인 고유 값을 사용하는 습관을 들이는 것이 중요합니다.

CSLOG에서 보기