우리는 리액트를 개발하다가, 특히 초기에 이런 경고문구를 많이 보았을 것이다.
Warning: Each child in a list should have a unique "key" prop. !!!
바로 map 등을 사용해서 컴포넌트를 반복적으로 생성할 때, key값을 지정해주지 않았다라는 경고이다.
그렇다면 이 key 값이 왜 필요할까?
리액트는 컴포넌트의 상태나 속성이 변할때마다 render 함수를 호출하는데, render 함수는 새로운 리액트 요소를 반환하고 이를 기존 요소 트리와 비교해 새로운 변경점에 대해서만 리렌더링을 수행합니다.
리액트는 O(N)의 시간복잡도로 두 트리를 비교하기 위해 key 속성을 사용하고, 자식 요소들을 반복적으로 렌더링하는 상황에서 명시적으로 key를 사용합니다.
즉, 트리를 비교할 때 key 값을 비교하고 key가 달라지면 렌더링도 다시하게 된다. 또한 key 값을 통해 추가되는 데이터 및 요소의 위치를 잡아주기도 합니다.
import { useState } from "react";
const StudyPage = () => {
const [data,setData]= useState([
{
id: 1,
name: "나름"
},
{
id:2,
name: "콘스트"
},
{
id:3,
name:'조이'
}
]);
const clickButton = () => {
setData([...data,{id:4,name:"합체"}])
console.log(data)
}
return(
<>
<button onClick={clickButton}> 추가 </button>
<ul>
{data?.map((item,index)=><li key={index}>{item.name}</li>)}
</ul>
</>
)
}
export default StudyPage;
일단 이 상황을 가정해서 예시를 들어보겠습니다.
key값을 index로 사용하고, 새로운 데이터를 뒤에 붙여보겠습니다.
마지막 데이터만 렌더링 되는것이 보이시나요? 이렇게 마지막 데이터를 추가하는 것은 문제가 되지 않습니다.
문제가 되는것은 데이터가 앞에 추가 될 때 입니다.
const clickButton = () => {
setData([{id:4,name:"합체"},...data])
console.log(data)
}
clickButton 함수를 이렇게 수정하면 새로운 데이터가 앞에 추가가 되겠죠?
그리고 전체가 리렌더링 되는 모습을 볼 수 있습니다.
<ul>
<li key=1>나름</li>
<li key=2>콘스트</li>
<li key=3>조이</li>
</ul>
에서 앞에 데이터가 추가 됬기 때문에 index가 변경되어
<ul>
<li key=1>합체</li>
<li key=2>나름</li>
<li key=3>콘스트</li>
<li key=4>조이</li>
</ul>
key 값이 변경되었기 때문에 react에서 다르다고 판단되어 리렌더링이 된것을 확인할 수 있습니다.
이래서 key값은 변경되지 않는 유일한 값으로 설정해야한다는 것입니다.
그래서 저는 1차원적으로 생각해서 key 값에 item 을 넣으면 데이터는 안변하니까 리렌더링이 안되겠지? 라고 생각이 들어 해보았습니다.
<>
<button onClick={clickButton}> 추가 </button>
<ul>
{data?.map((item)=><li key={item}>{item.name}</li>)}
</ul>
</>
이렇게 수정해보았다! 그 결과는?
짜란... 전체가 리렌더링 되었다. 그리고 경고메세지가 하나 떠있다.
경고: [object Object] 라는 동일한 키를 가진 두 개의 자식이 발생했습니다. 구성 요소가 업데이트 간에 ID를 유지하도록 키는 고유해야 합니다. 고유하지 않은 키를 사용하면 하위 항목이 중복 및/또는 생략될 수 있습니다. 이 동작은 지원되지 않으며 향후 버전에서 변경될 수 있습니다.
즉, react key에서는 무조건 문자열로 변환하기때문에, 객체를 문자열화하면 [object Object]가 되어 모든 키 값이 동일해진것이다. 그럼 react가 추가되야할 위치를 정확히 찾지 못하여 전체를 다시 렌더링하는 것입니다.
보통 서버에서 데이터를 받아오면 데이터에 해당하는 고유 idx 값이 있을것이다. 그 값을 사용하면 쉽게 해결됩니다.
예제에서 사용했던 데이터의 포맷은
[
{
id: 1,
name: "나름"
},
{
id:2,
name: "콘스트"
},
{
id:3,
name:'조이'
}
]
이러한 모습이기에 id 가 각 데이터의 고유값, 고유 idx이다. 이 id값을 key 값으로 지정하면,
<>
<button onClick={clickButton}> 추가 </button>
<ul>
{data?.map((item)=><li key={item.id}>{item.name}</li>)}
</ul>
</>
이러하겠다. 이 코드의 실행 결과를 보면
깔끔하게 추가된 부분만 데이터가 리랜더링됨을 알 수 있습니다.
이 key의 값이 전역적으로 고유할 필요는 없지만, 같이 반복되는 형제요소 내에서는 고유해야 react의 성능을 이처럼 망치지 않고 잘 사용할 수 있습니다.