본문 바로가기
React/이슈 해결

[react] React에서 key의 역할과 컴포넌트를 다시 그리는 과정

by 1two13 2023. 2. 15.
728x90
반응형

발생한 이슈


팀 프로젝트를 진행하면서 key에 대해 간과하고 있던 점을 스스로 발견해버렸다. 그래서 기록을 남기고자 이렇게 작성을 하려고 한다. 

 

나는 map을 사용할 때 key를 보통 map의 두 번째 인자인 index로 사용했다. 평소에는 문제가 되지 않았던 이슈가 이번 프로젝트에서는 이슈가 되어 돌아왔다. 제대로 정리하지 않으면 프로젝트를 할 때 헤맨다는 이유가 여기에서 나왔나 싶다,, 

 

여러 <div>들이 있고, 각각의 <div>에는 삭제 버튼이 있다. 삭제 버튼을 클릭했을 때 클릭한 <div>를 삭제하고 싶었다. 하지만 클릭한 <div>가 삭제되지 않고, 다른 <div>가 삭제되는 현상이 있었다. 

 

왜 이러한 이슈가 발생한 건지 정리해보려고 한다. 

 

 

 

Key의 역할


공식문서에서는 key에 대해 이렇게 설명하고 있다. 그 중 위의 이슈와 관련성이 깊은 내용만 뽑아왔다.

 

  • key는 react가 어떤 항목을 변경, 추가, 삭제할지 식별하는 것을 돕는다.
  • 항목의 순서가 바뀔 수 있는 경우 key에 index를 사용하는 것을 권장하지 않는다. 이로 인해 성능이 저하되거나 컴포넌트의 state와 관련된 문제가 발생할 수 있기 때문이다.

 

 

 

Key를 index로 사용하면 발생되는 문제


key를 index로 사용하면 위에서 언급한 이슈와 같은 상황이 발생한다.

여러 <div> 들이 있고, 각각의 <div>에는 삭제 버튼이 있다. 삭제 버튼을 클릭했을 때 클릭한 <div>를 삭제시키고 싶었다.
하지만 클릭한 <div>가 삭제되지 않고, 다른 <div>가 삭제되는 이슈가 발생한다.

 

왜 이러한 현상이 발생되는 걸까? 그 이유는 react의 재조정(Reconciliation)에 있다. 

728x90

 

 

재조정(Reconciliation)


공식문서를 살펴보면 재조정에 대해 자세하게 나와있다. 

 

간략하게 설명해보자면 react에서 state나 props가 갱신되면 render()함수를 다시 부르게 되고 새로운 DOM tree를 반환하게 된다. 이 때 react는 방금 만들어진 element를 효과적으로 브라우저에 갱신하기 위해 Virtual DOM이라는 변경된 tree에서 전체가 아닌, 변경된 tree 일부분만 변경해주는 자체적인 로직을 가지고 있다.

 

변경 사항을 확인하기 위해 Real DOM(기존 tree)과 Virtual DOM(변경된 tree)을 비교해주는 절차(Diffing Algorithm)를 가지게 되는데, 이 때 변경사항을 비교하는 기준으로 key를 사용한다. 

 

 

+ 따로 정리해둔 Virtual DOM 글을 참고해보면 조금 더 자세한 원리를 확인해볼 수 있다. 

 

React Virtual DOM이란?

DOM이란? 브라우저는 화면을 그리기 위해서 DOM(Document Object Model)이라는 개념을 사용한다. DOM은 HTML 파일 내용을 토대로 만들어지는데, JavaScript와 같은 언어로 수정할 수 있도록 만들어진 웹 페이

1two13.tistory.com

 

 

Diffing Algorithm

Diffing Algorithm은 아래와 같은 절차에 따라 동작한다.

 

1. 엘리먼트 타입이 달라지는 경우, react는 이전 트리를 버리고 완전히 새로운 트리를 구축한다. 

<div>
  <Counter />
</div>

<span>
  <Counter />
</span>

 

2. 엘리먼트 타입이 같은 경우, react는 동일한 내역은 유지하고 변경된 속성만 갱신한다. 

<div className="before" title="stuff" />

<div className="after" title="stuff" />

react는 DOM 노드 상에서 변경된 className만 수정을 한다.

반응형

이슈가 발생하게 된 이유


발생했던 이슈로 돌아와서 key를 index로 사용하고, 3개의 <div>가 있다고 가정해보자.

 

2번째 <div>를 삭제했을 때 react는 삭제한 2번째 컴포넌트가 key와 같은 값을 가지고 있으니깐 같은 컴포넌트라고 생각해 기존의 컴포넌트를 그대로 그려주게 된다. 그래서 2번째 컴포넌트를 삭제했음에도 불구하고 마지막 컴포넌트가 삭제되는 것이다.

 

다시 말해, react는 key가 동일할 경우, 동일한 DOM Element를 보여주기 때문에 이와 같은 예기치 못한 문제가 발생하게 되는 것이다. 

 

 

 

key로 그럼 어떤 값을 사용해야 할까?


간단하다. 고유한 값을 사용해주면 된다. 

 

하지만 고유한 값을 가지고 있지 않은 경우가 있을 수 있다! 나도 그랬다.

이런 경우에는 데이터 내부에서 고유한 id를 가질 수 있도록 만들어주는 것이 좋다. 

 

참고로 랜덤값을 key로 사용하는 경우도 있는데, 이 또한 권장하고 있는 방법이 아니다. 변하는 key를 사용하게 되면 많은 컴포넌트 인스턴스와 DOM 노드를 불필요하게 재생성하게 되어 성능이 나빠지거나 자식 컴포넌트의 state가 유실될 수 있기 때문이다.  

 

 

 

key를 index로 사용할 수 있는 경우


모든 경우에 key를 index로 사용할 수 없는 것은 아니다. 아래 3가지 조건을 만족한다면 사용해도 된다.

1. 배열과 각 요소가 변경되지 않는 경우

2. 데이터 내부에 id로 쓸만함 고유한 값이 없는 경우

3. 데이터가 변경되지 않는 경우

 

다시 말해 순서가 변하지 않고, 추가 또는 삭제가 되지 않는 경우라면 index를 key로 사용해도 문제는 없다. 

 

 

 

참고자료


728x90
반응형

댓글