개발 이야기/React

React, Key 속성을 왜 사용하는지 알고 사용하자

2023. 7. 19. 21:37

eslint중 eslint-plugin-react에서 출력되는 경고 메시지이다.

 

Missing "key" prop for element in iterator

 

엘리먼트 태그에 key 속성을 부여하라는 의미이다.

 

이 메시지는 우리가 배열을 통해서 Element를 처리할 때 흔히 마주칠 수 있는 에러 문구중 하나이며 꽤나 자주 볼 수 있는 문제임에도 불구하고 왜 그런지 모르는 사람들이 생각보다 많았고 이게 어떤 문제를 야기하는지 모르는 사람들이 정말 많았기에 자세히 풀어보려 한다.

 

const Page = () => {
  const [getArr, setArr] = useState(['first', 'second']);

  return <ul>
    {getArr.map((item) => (
      <li>{item}</li>
    ))}
  </ul>
};

arr 이란 배열을 <ul> 태그 내에서 재귀적으로 처리하고 있는 간단한 컴포넌트가 있다고 가정해보자.

 

<ul>
  <li>first</li>
  <li>second</li>
</ul>

실행 결과는 다음과 같을 것이고 이는 아마 의도된 대로 잘 처리되고 있음을 알 수 있을 것이다.

하지만 eslint 에서는 key 값이 없다고 경고문을 출력하게 된다.

 

const Page = () => {
  const [getArr, setArr] = useState(['first', 'second']);

  return <ul>
    {getArr.map((item) => (
      <li key={item}>{item}</li>
    ))}
  </ul>
};

다음과 같이 <li>에 key 속성을 부여했더니 더 이상 경고문이 출력되지 않는다.

이 이유를 알고 싶다면 근본적인 원인부터 알아봐야 한다.

 

React를 사용하는 사람들은 모두 알다시피 Virtual DOM을 기반으로 하여 변경된 부분을 찾아 재렌더링하는 과정을 거치는데 이런 배열을 기반으로한 재귀 호출의 경우 배열이 변경될 때마다 DOM Tree가 변경되며 여기에서 부수적으로 발생하는 연산이 매번 갱신하고 처리하기에는 많은 비용이 소모된다. 이를 위해 React에서 고안해낸 것이 key 속성인데 key를 통해 배열 상태를 저장하고 필요한 연산만 처리하고 중복되고 필요없는 연산은 배제하겠다는 것이다.

 

말이 어렵다면 아래 예시를 보자.

 

// 코드 예시
const Page = () => {
  const [getArr, setArr] = useState(['first', 'second']);

  return (
    <section>
      <ul>
        {getArr.map((item) => (
          <li>{item}</li>
        ))}
      </ul>

      <button onClick={() => setArr(['first', 'second', 'third'])}>RESET ARR</button>
    </section>
  );
};
// 버튼을 누르기 전
<ul>
  <li>first</li>
  <li>second</li>
</ul>

// 버튼을 눌렀을 때
<ul>
  <li>first</li>
  <li>second</li>
  <li>third</li> // <<< 자식 NODE 추가
</ul>

코드를 잘 보면 버튼을 눌렀을 경우 배열의 마지막에 "third"를 추가하여 다시 재렌더링을 하는 컴포넌트임을 알 수 있다. 사실 여기서의 프로세스만 보면 key를 삽입하지 않아도 아무 문제가 없는 것이 key를 삽입하지 않으면 React에서 직접 배열의 index를 key에 삽입한다. 그리고 <ul> Element의 자식들중 마지막에만 <li>third</li> 엘리먼트만 추가하기만 하면 되기 때문에 순차적으로 처리된다.

 

const Page = () => {
  const [getArr, setArr] = useState(['first', 'second']);

  return (
    <section>
      <ul>
        {getArr.map((item) => (
          <li>{item}</li>
        ))}
      </ul>

      <button onClick={() => setArr(['zero', 'first', 'second'])}>RESET ARR</button>
    </section>
  );
};
// 버튼을 누르기 전
<ul>
  <li>first</li>
  <li>second</li>
</ul>

// 버튼을 눌렀을 때
<ul>
  <li>zero</li>
  // ----------------- <<< 구조 파괴
  <li>first</li>
  <li>second</li>
</ul>

하지만 이 상황을 보자, 버튼을 눌렀을 때 배열의 맨 앞에 "zero"를 추가하여 기존 Node들을 분해하고 재구성하고 있다. 이를 통해 first, second를 가지던 Element는 제거되고 다시 만들어지게 된다.

 

이와 같은 일이 발생하면 안되는 것이 first나 second를 가진 Element의 경우 순서만 변경됐을 뿐이지, 다시 새롭게 삭제하고 생성하는 작업하기을 진행하기에는 너무 비효율적이다. 따라서 고유하면서도 식별할 수 있는 key를 삽입하여 이런 부수적인 작업을 방지하는 것이다.

 

다음은 key 속성을 부여한 코드이다.

const Page = () => {
  const [getArr, setArr] = useState(['first', 'second']);

  return (
    <section>
      <ul>
        {getArr.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>

      <button onClick={() => setArr(['zero', 'first', 'second'])}>RESET ARR</button>
    </section>
  );
};
// 버튼을 누르기 전
<ul>
  <li key="first">first</li>
  <li key="second">second</li>
</ul>

// 버튼을 눌렀을 때
<ul>
  <li key="zero">zero</li> // <<< 이전 배열에 "zero" key가 없음으로 새롭게 Node 생성
  <li key="first">first</li> // <<< 이전 배열에 "first" key가 있음으로 순서만 변경
  <li key="second">second</li> // <<< 이전 배열에 "second" key가 있음으로 순서만 변경
</ul>

이렇게 key 속성을 부여하면 배열이 변경되어도 이전에 렌더링된 결과물과 비교하여 key 값이 동일할 경우 DOM Tree 순서를 변경할 뿐, Element를 다시 생성하거나 삭제하는 부수적인 작업을 처리하지 않는다. 작업이 얼마나 낭비가 심한지 생각해보면 꽤나 큰 차이를 느낄 수 있다.

 

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

 

단, key는 항상 중복되는 값이 없도록 해야 하며 중복될 경우 Child Element들의 고유한 State를 잃어버릴 수도 있다.

대신 key는 전역적으로 특별하지 않아도 되며 Element 형제 사이에서만 유일하면 된다.

 

마지막으로 최후의 수단인 셈 index를 사용해도 되지만 되도록 재배열되지 않는 배열에서만 사용하기를 권장하며 index를 사용하면 재배열이 되는 순간 Element들이 재사용되지 않고 삭제되고 재생성됨을 유의해야 한다. 이 말인 즉슨, 어떤 Element에 체인된 메소드나 State값을 잃어버려 의도치 않는 동작과 오류를 마주할 수 있다는 것이다.

 

'개발 이야기 > React' 카테고리의 다른 글

React(리액트)는 Storybook(스토리북)을 적극 사용하자.  (0) 2023.07.05
육각형 그래프 애니메이션을 구현해보았다.  (0) 2023.06.03
한글 타이핑 시스템을 구현해보았다.  (0) 2023.06.02
'개발 이야기/React' 카테고리의 다른 글
  • React(리액트)는 Storybook(스토리북)을 적극 사용하자.
  • 육각형 그래프 애니메이션을 구현해보았다.
  • 한글 타이핑 시스템을 구현해보았다.
S.H.S
S.H.S
한또리의 일기장
한또리의 일기장한또리의 일기장
S.H.S
한또리의 일기장
전체
오늘
어제
  • 분류 전체보기 (35)
    • 개발 이야기 (1)
      • JavaScript (4)
      • TypeScript (0)
      • React (4)
      • Git (3)
      • Next.js (0)
      • Pattern Matching (1)
      • Terminal (1)
      • AWS (1)
      • Unity (0)
      • Python (0)
      • Ubuntu (0)
      • Aduino (0)
    • 즐거운 게임 수학 (9)
    • 개발자 면접 후기 (7)
    • 일상 (4)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • 거리
  • 컴포넌트 렌더링
  • 패키지 관리자
  • 프론트엔드 면접
  • 이색테마
  • 계산
  • code-owners
  • 각도
  • git
  • 컴포넌트 기반
  • 개발자
  • 수염 자국
  • 신박함
  • 수학
  • 애니메이션
  • 회사
  • react
  • 원
  • 컴포넌트 시각화
  • 프론트엔드

최근 댓글

최근 글

hELLO · Designed By 정상우.
S.H.S
React, Key 속성을 왜 사용하는지 알고 사용하자
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.