언젠가는 펼쳐 볼 아카이브

[ReactJS] 너는 key를 소중하게 여기지 않았지 : React에서 "key"의 역할과 중요성 본문

IT/ReactJS

[ReactJS] 너는 key를 소중하게 여기지 않았지 : React에서 "key"의 역할과 중요성

개발자희망생고롸파덕 2024. 3. 21. 02:21

# 우리 어디서 본적 있지 않나요🤔 

리액트로 프로젝트를 개발하다보면, 크롬 개발자 콘솔창에서 자주 보이는 "warning" 친구가 있다. 

경고문 예시 코드
import data from './data.json';

function App() {
  return (
    <div>
      {data.map((item) => {
        return (
        {/* "p" 태그에 key값을 설정해 주지 않음*/}
          <p>
            Name : {item.name}, Age : {item.age}
          </p>
        );
      })}
    </div>
  );
}

export default App;

 

바로 이 친구인데, 리액트 컴포넌트를 생성하는 방법 중 데이터 값을 불러와 반복문으로 child component를 생성할 때 key 값을 설정하는 걸 놓치면 생기는 warning 친구다. 

 

프로젝트를 개발 하다보면 종종 놓칠 수 있는 게 key 인데, 나 같은 경우엔 콘솔에 새빨간 warning이 찍힌 것을 보고 "아, 저거 놓쳤구나." 하고 관습적으로 해당 코드에 가서 key 값을 넣어주곤 했다.

 

key값으로 애를 먹은 적이 없으니, 딱히 중요하게 생각하지 않고 "유니크한 값을 넣으면 되겠지 뭐..🙄" 라는 심정으로 보통 불러온 데이터의 고유 id를 key값으로 주고 넘겨버린 나.. 

 

이번에 받은 코딩 과제 프로젝트를 하면서도 별 생각 없이 key 값을 설정했다가, 업보를 그대로 돌려받았다😭 

 

"아니, 로직엔 문제가 없는데.. 왜 자꾸 사라져야할 컴포넌트가 계속 붙어있는거야?"

 

B 카테고리에서 데이터를 불러온 후, 다른 카테고리를 가면.. 이상하게도 사라져야 할 "B" 카테고리의 child component가 남아있는 현상이 있었다.  다른 카테고리를 넘어갈때 B 카테고리의 데이터를 불러오는 것도 아닌데, 왜 계속 남아있는건지!  로직을 고쳐보고, 데이터 패치 하는 api도 고쳐봐도 이상하게 "B" 카테고리의 그 친구만 계속 살아남아있었다.

 

 

- 뭐가 문제일까? 로직은 문제가 없는데 왜 B 카테고리에서 다른 카테고리로 넘어갈 때만 저런 현상이 있는거지?

- 설마 데이터 문제인가? 데이터 구조는 다른 카테고리들이랑 비슷한데.

- 다른거 뿐이라곤 B 카테고리 데이터만 중복된 제목들이 주르륵 있는것 뿐인데.. 설마 이거 때문인가?

 

그리고 다시 읽어본.. 나의 콘솔 로그.

Each child in a list should have a unique "key" props.

[ React : 너 나한테 중복된 key 줬어!! 😡]

 

B 카테고리의 dummy data는 서비스 공지 사항과 관련된 내용으로 데이터를 넣어주다 보니.. 제목이 겹치는 경우가 많았다. (이게 버그의 씨앗이었고...)

카테고리 별 데이터 리스트 뷰를 뿌려줄 때, 생각없이 child component의 key 값을 "key={`bCategory_{data.title}`}" 이런식으로 넣어놨더니 key 값이 중복되는!!! 상황이 생겨버린 것!!!🤦‍♀️

 

key 값을 중복되지 않게 이슈를 해결하고나서... 왜 key값이 중복되면 안되는건지 복습할 겸,두 번 다시는 이런 초보적(?)인 실수를 하지 않기 위해 글을 남겨두려고 한다. 

 

#혹시 재조정(reconciliation)을 아세요? 

리액트는 state나 props가 갱신되면, 이걸 캐치해 새로운 DOM tree를 그리고 반환하는 재조정(reconciilation)을 진행한다. 이때, virtual DOM과 real DOM을 비교하는 절차가 있다.  

그런데 일일dl virtual DOM과 real DOM을 비교하게 된다면 O(n^3) 만큼의 소요되는 비교가 필요한데, 리액트에서는 이걸  O(n)으로 줄이기 위해 제한 조건을 걸어뒀다!

 

1. 서로 다른 타입의 두 element는 서로 다른 트리를 만들어낸다

2. 개발자가 key prop을 통해, 여러 랜더링 사이에서 어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.

 

이런 제한 조건의 자세하고 다양한 내용은 리액트 재조정 관련 공식 문서 에서 볼 수 있고, 이 글에서 주목해야 하는 부분은 "자식에 대한 재귀적 처리" 이다.

 

⭐자식에 대한 재귀적 처리⭐

 

react는 DOM 노드의 자식들을 재귀적으로 처리할 때, 리액트는 동시에 두 리스트를 순회하고 차이점이 있으면 변경된 tree를 생성한다. 

이때 리액트에서 자식들이 "key"를 가지고 있다면, "key"를 통해 기존 트리와 이후 트리의 자식이 일치하는지 확인한다!

## 이전
<ul>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

## 이후
<ul>
  <li>Connecticut</li>
  <li>Duke</li>
  <li>Villanova</li>
</ul>

>> 이 경우 key 값이 없기 때문에 "Duke", "Villanova"를 담고 있는 li가 같은 component 더라도,
기존의 트리는 삭제되고 새로운 컴포넌트로 다시 처음부터 그려지게 됨

 

## 이전
<ul>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

## 이후
<ul>
  <li key="2014">Connecticut</li>
  <li key="2015">Duke</li>
  <li key="2016">Villanova</li>
</ul>

>> 하지만 이렇게 key가 있다면? 같은 key를 가진 component는 그대로 두고, 없던 것만 새로 붙이면 된다!

 

 

자.. 그럼 재조정에 대해서 여기까지 알아보고, 내가 만났던 이슈와 어떤 연관성이 있는지 처음으로 돌아와보자.

리액트를 공부할 때  'key' 와 관련해서 꼭 언급되는 것이 있다.

 

1. 리액트에서 "key"라는 녀석은 element list를 만들 때 포함해야 한다.

2. "key"는 리액트가 어떤 항목을 변경, 추가 또는 삭제할지 식별하기 위해 사용하는 것.

3. "key"는 중복되지 않고 유니크한 값을 가져야 한다.

4. "key"값에 index를 넣는 것은 권장하지 않는다. 

 

여기서 내가 만들었던(ㅠㅠ) 버그는 3번에 해당하는 이슈였는데,  "key={`bCategory_{data.title}`}" 로 설정해서 제목이 같은 component들이 우수수 생겨버린 것이다. 🙃 

동일한 key들을 가지고 있으니, 리액트 입장에서는 "어? 키값이 이전에 있던건데? 그럼 같은 컴포넌트네!" 라고 생각해버리고 기존 컴포넌트를 그대로 그려버리는 현상이 나타나는 것이다. 🤦‍♀️🤦‍♂️

 

이런 예상치 못한 버그가 발생할 수 있으니, Key 값은 꼭!! 꼭!! 유니크한 값으로 넣어야한다

 

 

#근데.. 4번은 왜.. "key"는 index로 사용하는 걸 권장하지 않나요?

## React 공식 문서에서 발췌
인덱스를 key로 사용 중 배열이 재배열되면 컴포넌트의 state와 관련된 문제가 발생할 수 있습니다.
컴포넌트 인스턴스는 key를 기반으로 갱신되고 재사용됩니다. 
인덱스를 key로 사용하면, 항목의 순서가 바뀌었을 때 key 또한 바뀔 것입니다. 
그 결과로, 컴포넌트의 state가 엉망이 되거나 의도하지 않은 방식으로 바뀔 수도 있습니다.

 

 

 

#참고