React는 DOM의 요소들을 map이나 from처럼 순회하면서 렌더링할 경우, key를 넣어주라고 경고한다.
예를 들어 3개의 리스트를 가진 변수를 통해 key가 없이 배열 랜더링을 진행하게 한다면 해당 리스트 변수에 1개 더 추가되는 경우라도 React 는 총 4개를 처음부터 다시 리 렌더링 하게 된다.
하지만 key 를 지정한다면 기존의 요소들은 변경되지 않았다는 걸 React 에서 자동으로 파악 후 새로 생기는 요소만 리 렌더링을 진행하게 된다.
단순히 key 요소만 추가한 것만으로도 더욱 최적화 된 랜더링을 진행할수 있다.
그럼 왜 map이나 from의 배열 index를 사용하지 말라고 하는 것일까?
다음 예시를 통해 알아보자.
import React, { useState } from "react";
const KeyEx = () => {
const [list, setList] = useState([
{ name: "철수" },
{ name: "영희" },
{ name: "기철" },
]);
const addList = () => {
setList([{ name: "영수" }, ...list]);
};
const delList = () => {
setList( list.filter( item => {
return item.name !== "철수";
} ));
}
return (
<div className="wrapper">
<button onClick={addList}>추가하기</button>
<button onClick={delList}>삭제하기</button>
{list.map((item, index) => {
return <div>{index} : {item.name} <input type="text"/></div>
})}
</div>
);
};
export default KeyEx;
이렇게 단순히 “영수”를 리스트 앞에 넣고 빼는 코드를 작성해보았다.
이 상태에서 추가하기 버튼을 누르면 “철수”가 밀려나고 “영수”가 생기면서 “아아아”는 철수와 함께 두번째 열에 생길 것으로 예상했다.
<예상 결과>
<실제 결과>
이유가 무엇인지 살펴보자. 추가 버튼을 누르기 전 상황에서 DOM의 key가 0
인 곳에는 “아아아” 가 있다.
하지만 추가 버튼을 누르게 되면 list의 데이터가 변경되면서 compoent가 Re-render 되고, index에 맞게 다시 리스트를 생성한다. 결국 “영수”의 데이터
에 key가 0
으로 바뀌게 되는데,
React는 key가 동일할 경우, 동일한 DOM element를 보여주기 때문에 “아아아”는 0번째 key
에 존재하게 된다.
데이터를 삭제할 때도 동일한 문제가 발생한다.
const delList = () => {
setList( list.filter( item => {
return item.name !== "철수";
} ));
}
name이 철수가 아닌
리스트만 필터링하기 때문에 삭제시, 예상 결과는
이라고 생각했다. 하지만 React는 key가 동일할 경우, 동일한 DOM element를 보여주기 때문에 ****
key가 0인 데이터는 영희에 씌워지게 되고, key가 1인 데이터는 기철에게 씌워지게 된다.
결국 고유한 값을 가질 수 있도록 list가 변경되지 않을 때만 쓰라는 말로 보인다.
DB의 고유 ID 값을 사용하자
대부분 DB에서 고유의 ID로 Data를 넘겨준다. 이럴 때 Data와 고유 ID는 한쌍으로 넘어오기 때문에 이용할 수 있다.
nanoid()를 사용하자
DB에서 고유한 ID를 주지 않을 경우, nanoid
패키지에서 제공하는 nanoId를 사용하여 key 값을 넣어주면 된다.
yarn add nanoid
해당 yarn 으로 프로젝트에 패키지를 설치하고
import { nanoid } from "nanoId";
Import 후에 key에 nanoId를 사용한다.
import React, { useState } from "react";
import { nanoid } from "nanoid";
const KeyEx = () => {
const [list, setList] = useState([
{ name: "철수" },
{ name: "영희" },
{ name: "기철" },
]);
const addList = () => {
setList([{ name: "영수" }, ...list]);
};
const delList = () => {
setList(
list.filter((item) => {
return item.name !== "철수";
})
);
};
return (
<div className="wrapper">
<button onClick={addList}>추가하기</button>
<button onClick={delList}>삭제하기</button>
{list.map((item, index) => {
return (
<div key={nanoid()}>
{index} : {item.name} <input type="text" />
</div>
);
})}
</div>
);
};
export default KeyEx;
shortid
를 사용하는 방법도 있는데, nanoid 보다 효율성이 덜어진다고 한다.