Key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 돕는다.
리액트는 최적화를 위해 key값을 가지고 렌더링을 한다. 컴포넌트 리스트를 렌더링 했을 때 어떤 요소에 변동이 있었는지 알아내기 위해 사용한다. key가 없는 경우엔 가상 DOM을 비교하는 과정에서 리스트를 순차적으로 비교하여 변화를 감지하는데, key가 있다면 이 값을 사용하여 어떤 변화가 있었는지 더욱 빠르게 알아낼 수 있다.
기존 트리를 가지고 다른 트리로 변환하기 위해 최소한의 연산을 하는 알고리즘 문제를 해결하기 위해 일반적으로 O(n^3)의 시간 복잡도를 가진다. 이대로 리액트에 적용한다면, 1000개의 엘리먼트를 그리기 위해 10억 번의 비교 연산을 해야하며, 이는 너무 비싼 연산이라고 리액트에서는 설명한다.
따라서, 리액트에는 두 가지 가정을 통한 휴리스틱 알고리즘을 적용하여 이 연산을 O(n)까지 줄일 수 있었다고 설명한다.
- 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.
- 개발자가 key prop을 통해, 여러 렌더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.
이 때, 사용되는 것이 key prop이다. 리액트에서는 key prop을 통해 컴포넌트를 새로 렌더하면서 엘리먼트 트리를 생성할 때, 더 빠르게 비교 연산을 수행하고 렌더할 수 있다.
리스트를 렌더할 때는 각 아이템이 고유한 id를 가질 수 있도록 하고, 해당 id를 key로 지정하는 것이 좋다.
key는 리스트의 순서를 비교하는 데 사용하는 값이며, 리스트를 렌더하고 있는 곳에서 key 를 넣어주어야 한다.
map 함수 내부에 있는 엘리먼트에 key를 넣어주는 것이 가장 일반적이다.
function ListItem(props) {
// 이 곳에서 li에 key 값을 지정해도 별 효과는 없다.
return <li key={props.id}>{props.value}</li>
}
function List(props) {
const data = props.data;
// 다음과 같이 ListItem 엘리먼트가 key를 가져야 한다.
// 참고: key는 ListItem 엘리먼트의 prop으로 전달되지 않는다.
return (
<ul>
{data.map((item) => <ListItem key={item.id} />}
</ul>
);
}
렌더할 때마다 nanoid나 uuid가 실행되지 않도록 주의하자. 렌더할 때마다 nanoid가 실행되는 불필요한 연산이 이루어질 뿐만 아니라, 기존 리스트와 비교하려는 시도가 무의미해지는 안티 패턴이다.
function List(props) {
const data = props.data;
return (
<ul>
{data.map((item) => <ListItem key={**nanoid(8)**} /> /* <- DON'T DO THAT */}
</ul>
);
}
map 함수의 index 값을 key로 설정하면, map 함수가 다시 실행되면서 index도 다시 생성된다. 이렇게 될 경우, 기존 리스트와 비교해서 최적화하려는 시도 자체가 무의미해진다. 즉, 안티 패턴이다.
key값을 지정하지 않았을 때 리액트에서는 기본적으로 index를 key로 사용한다. 따라서 key값을 반드시 지정해주도록 하자!