React 에서 배열컴포넌트 사용시 key 에 배열의 index는 가급적 사용하지 말자.!!
React 에서의 key
key
는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 도와줍니다. key
는 엘리먼트 or 컴포넌트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 합니다. 이때 key
속성에는 해당 배열 내부에 각기 고유(uniquq) 값을 넣어 주어야 합니다.
list key 렌더링 예시
const numbers = [1, 2, 3, 4, 5]; const listItems = numbers.map((number) => <li key={number.toString()}> {number} </li> );
key를 지정해야 하는 이유
예를들어 3개의 리스트를 가진 변수를 통해 key가 없이 배열 랜더링을 진행하게 한다면 해당 리스트변수에 1개가 더 추가되는 경우라도 React 는 총 4개를 처음부터 다시 리렌더링 하게 됩니다. 하지만 key 를 지정한다면 기존의 요소들은 변경되지 않았다는걸 React 에서 자동으로 파악 후 새로생기는 요소에 대해서만 리렌더링을 진행하게 됩니다. 단순히 key 요소만 추가한것만으로도 더욱 최적화 된 랜더링을 진행할수 있습니다. :)
map 속성에서의 index를 통한 키 주입
배열을 통한 컴포넌트를 만들어야 하는 경우 해당 object 에 고유값이 무엇인지 모를 경우 map의 두번째 인자인 index 속성을 통하여 다음과 같이 key의 고유값을 지정하곤 합니다.
const arrayData = [{...},{...}]; const items = arrayData.map((obj, index) => <li key={index}> {obj.name} </li> );
해당 예시대로 작성할 경우 단순한 리스트 렌더링 역활만 한다면 문제가 생기지 않지만 정렬, 추가, 삭제 등의 작업이 있는 경우 예상치 못한 문제가 발생할 수 있습니다.
배열의 index 를 통한 키주입시 생기는 문제
우선 해당 문제를 파악하기 위해 우선 관련 예시코드를 작성해 보도록 하겠습니다.
예시 컴포넌트
사람들에 대한 리스트를 출력하도록 하고 이떄 map의 index 로 key를 잡도록 작성하였습니다. 또한 사람 추가 버튼을 클릭하면 추가된시람이 0번째 요소로 추가되도록 설정하였습니다.
import React, { useState } from 'react'; function PersonList() { const [personList, setPersonList] = useState([{ name: '성인'}, { name: '혜진' }]); // 사람 추가 이벤트 const addPerson = () => { setPersonList((prev) => [{ name: '추가된사람', description: '추가된 사람 입니다.' }, ...prev]); }; return ( <div> <h2>배열 index key 예시</h2> <button onClick={addPerson}>사람 추가</button> {personList.map((person, index) => { return ( <div key={index}> <span>{person.name}</span> <input type='text' /> </div> ); })} </div> ); } export default PersonList;
실행 화면
위의 컴포넌트 예시를 실행하면 다음과 같이 보이게 됩니다.
이후 해당 컴포넌트의 input 영역에 다음과 같이 영역을 채워주도록 하겠습니다.
이제 사람추가 버튼을 클릭하여 새로운 사람을 추가해준 후 결과를 보도록 하겠습니다.
??! 성인 영역에 있던 input 영역이 새로 추가한 쪽으로 이동하고 성인 쪽 영역에는 인풋이 초기화 되었습니다...
왜 이런 일이 발생하였는가?
사람추가 버튼을 누르게 되면 배열이 새로 바뀌게 되면서 컴포넌트가 리렌더링 이 되고 이때 index를 다시 매핑하게 됩니다. 이때 기존에는 index 0번이 성인 이였지만 다음번의 0번째 요소는 추가된 사람이 됩니다. React는 key가 동일 할 경우, 동일한 DOM Element를 보여주기 때문에 위와 같이 예상치 못한 문제가 발생합니다.
리스트의 아이템 요소에 유니크 값이 없는경우 해결하기.
대부분의 경우는 각 배열 요소의 고유한 유니크 키 값이 존재할테지만 간혹 이렇지 못한 경우가 존재하기 마련입니다. 이런 경우는 nanoid 와 같은 uniqueID 생성 라이브러리를 통해 key 속성을 주입하면 됩니다.
nanoId 를 사용한 key 주입 예시
import React, { useState } from 'react'; import { nanoid } from 'nanoid'; function PersonList() { const [personList, setPersonList] = useState([{ name: '성인'}, { name: '혜진' }]); // 사람 추가 이벤트 const addPerson = () => { setPersonList((prev) => [{ name: '추가된사람', description: '추가된 사람 입니다.' }, ...prev]); }; return ( <div> <h2>배열 index key 예시</h2> <button onClick={addPerson}>사람 추가</button> {personList.map((person, index) => { return ( // nanoid 를 사용한 고유 key 부여 <div key={nanoid()}> <span>{person.name}</span> <input type='text' /> </div> ); })} </div> ); } export default PersonList;
index 요소는 그럼 반드시 사용하면 안되는 걸까요?
아닙니다.~ 물론 배열의 요소가 필터링, 정렬 삭제, 추가 등의 기능이 들어간다면 문제가 발생할수 있으나 다음과 같은 경우에서는 index로 사용해도 무방합니다. 다만 개인적으로는 코드의 일관성을 위해 최대한 index 를 사용 안하는 것을 추천드립니다. :)
- 배열과 각 요소가 수정, 삭제, 추가 등의 기능이 없는 단순 렌더링만 담당하는 경우
- id로 쓸만한 unique 값이 없을 경우
- 정렬 혹은 필터 요소가 없어야 함