[React] React.js 강좌 6. 리스트 렌더링과 Key 속성의 원리
1. 리스트 렌더링이란?
리액트에서 배열 데이터를 화면에 표시하려면
단순히 하나씩 나열하는 대신 **반복문(map)**을 이용해 렌더링합니다.
예를 들어, 사용자 목록을 렌더링한다고 가정해봅시다.
function UserList() {
const users = ['기범', '지수', '민준', '수현'];
return (
<ul>
{users.map((user) => (
<li>{user}</li>
))}
</ul>
);
}
이렇게 하면 users 배열의 각 요소가 <li> 형태로 출력됩니다.
리액트는 map()의 반환값을 자동으로 인식해, JSX 요소 리스트로 변환합니다.
하지만 여기서 중요한 점은 — 모든 반복된 요소에는 key가 필요하다는 것입니다.
2. Key 속성이 필요한 이유
리액트는 Virtual DOM을 사용하여 화면을 효율적으로 업데이트합니다.
이 과정에서 어떤 요소가 추가, 변경, 삭제되었는지를 판단해야 합니다.
이때 key 속성은 리액트가 각 요소를 고유하게 식별할 수 있도록 도와줍니다.
function UserList() {
const users = ['기범', '지수', '민준', '수현'];
return (
<ul>
{users.map((user, index) => (
<li key={index}>{user}</li>
))}
</ul>
);
}
여기서 key={index}를 넣으면 경고 메시지는 사라집니다.
하지만 단순히 인덱스를 key로 사용하는 건 항상 좋은 방법은 아닙니다.
3. 인덱스를 key로 쓰면 안 되는 이유
리액트는 key를 기준으로 DOM을 비교(diffing)합니다.
그런데 배열의 순서가 바뀌거나 중간에 요소가 추가/삭제되면
인덱스를 key로 사용했을 때 잘못된 업데이트가 일어날 수 있습니다.
예를 들어, 다음과 같은 상황을 봅시다.
const users = ['기범', '지수', '민준'];
처음 렌더링된 key는 [0, 1, 2]입니다.
이후 배열이 이렇게 바뀌었다고 가정해봅시다.
const users = ['지수', '기범', '민준'];
리액트는 인덱스(0, 1, 2)를 기준으로 비교하므로
“내용이 바뀌었다”는 걸 인식하지 못하고
DOM을 잘못 업데이트할 수 있습니다.
따라서 **항상 데이터 자체가 가진 고유한 값(예: id)**을 key로 사용해야 합니다.
4. 고유 ID를 key로 사용하는 예시
function UserList() {
const users = [
{ id: 1, name: '기범' },
{ id: 2, name: '지수' },
{ id: 3, name: '민준' },
];
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
이 방식은 각 항목이 배열 순서에 상관없이
고유한 id를 기반으로 비교되므로 안정적입니다.
5. 중첩 리스트 렌더링
리스트 안에 또 다른 리스트가 있는 경우에도 동일한 원리가 적용됩니다.
function CategoryList() {
const categories = [
{ id: 1, name: '프론트엔드', items: ['HTML', 'CSS', 'React'] },
{ id: 2, name: '백엔드', items: ['Node.js', 'NestJS', 'Spring'] },
];
return (
<div>
{categories.map((category) => (
<div key={category.id}>
<h3>{category.name}</h3>
<ul>
{category.items.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
))}
</div>
);
}
리스트가 중첩되어 있을 때도 각 단계마다 key를 부여해야
리액트가 변경 사항을 정확히 감지할 수 있습니다.
6. Key가 없을 때 생기는 문제
key를 생략하면 리액트는 다음과 같은 경고를 띄웁니다.
Warning: Each child in a list should have a unique "key" prop.
이 경고는 단순한 주의사항이 아니라,
성능 저하와 렌더링 오류를 방지하기 위한 중요한 신호입니다.
key가 없으면 리액트는 모든 요소를 다시 그려야 하므로
불필요한 리렌더링이 발생하게 됩니다.
7. map() 대신 다른 반복문을 쓰면 안 되나요?
리액트에서 JSX를 반환하려면 배열 형태로 결과를 만들어야 합니다.
forEach는 값을 반환하지 않기 때문에 JSX에서 사용할 수 없습니다.
즉, 반드시 **map()**을 사용해야 합니다.
const list = items.map((item) => <li key={item.id}>{item.name}</li>);
이처럼 map은 각 요소를 순회하면서 새로운 배열을 반환하기 때문에,
리액트가 리스트를 렌더링할 때 이상적으로 동작합니다.
8. 리스트 렌더링 시 주의할 점
- 반복되는 모든 JSX 요소에는 고유한 key를 부여한다.
- key는 **항상 고유하고 예측 가능한 값(id 등)**을 사용한다.
- index를 key로 사용하는 것은 정렬/추가/삭제가 없는 경우에만 예외적으로 허용한다.
- 중첩된 리스트일 경우, 각 map 단계마다 key를 설정해야 한다.
9. 마무리
이번 글에서는 리액트의 리스트 렌더링과 key의 중요성을 다뤘습니다.
- map()을 사용하여 JSX 리스트를 렌더링한다.
- key는 리액트가 각 요소를 구분하는 기준이 된다.
- 고유한 id를 key로 사용하면 효율적인 DOM 업데이트가 가능하다.
다음 글에서는 **조건부 렌더링(Conditional Rendering)**을 살펴보겠습니다.
상태나 조건에 따라 다른 UI를 보여주는 방법을 단계별로 정리해보겠습니다.