본문 바로가기
Web/React

React의 key (Diffing 알고리즘과 framer-motion)

by 가나닩 2024. 10. 23.

처음 React 개발을 시작하고 key값을 신경쓰지 않은채로 수많은 컴포넌트들을 집어넣다보면 key와 관련된 경고문을 볼 수 있다.

 

React에서 key값은 매우 중요하게 사용된다.

  • 컴포넌트 식별 및 업데이트 최적화 (Diffing 알고리즘)
  • framer-motion 등 라이브러리에서의 컴포넌트 구분

 

 

1. 컴포넌트 식별 및 업데이트 최적화

React가 컴포넌트를 생성하고 제거할때 개발자가 JSX 상에 마크업한 위치를 기준으로 작동하는것이 아닌 UI 트리에서의 위치를 기준으로 작동한다.

export default function Test() {
    const test = true;

    if (test) return <ComponentTest name="kim" />;
    return <ComponentTest name="park" />;
}

function ComponentTest({ name }: { name: string }) {
    return <h1>{name}</h1>;
}

key값이 따로 없어 두 개의 ComponentTest는 같은 컴포넌트로 처리한다.

 

test라는 변수의 boolean값에 따라 렌더링되는 컴포넌트를 따로 작성해주었다. 조건에 따라 return문이 두번 작성되어 ComponentTest라는 컴포넌트가 따로 관리되는것으로 보이지만 앞서 말했다시피 React는 JSX의 마크업 위치를 고려하지 않고 UI 트리에서의 위치만을 고려한다.

 

즉 test값이 true혹은 false로 여러번 변경되더라도 ComponentTest라는 같은 컴포넌트가 같은 자리에 남아있기때문에 React는 컴포넌트를 제거한뒤 생성하지 않고 유지한채로 props 값만을 변경시킨다.

 

하지만 여러가지 컴포넌트를 다루다 보면 같은 UI트리로 렌더링되는 컴포넌트 구조라도 따로 관리해야하는 상황이 존재한다. 이때 같은 위치라도 다른 컴포넌트인것을 React에 알려주기 위해 key값을 사용한다.

 

 

export default function Test() {
    const test = true;

    if (test) return <ComponentTest key="kim" name="kim" />;
    return <ComponentTest key="park" name="park" />;
}

function ComponentTest({ name }: { name: string }) {
    return <h1>{name}</h1>;
}

key값이 다르므로 두 개의 ComponentTest는 각각 다른 컴포넌트로 처리된다.

 

key값을 각각 다르게 주면 React는 내부적으로 두 컴포넌트를 다르게 처리하여 test값이 변경될때 기존 컴포넌트를 제거하고 다시 생성한다.

 

이러한 React의 작동방식은 Diffing 알고리즘을 통해 UI트리를 지속적으로 비교하는 React의 특성상 사용방법에 따라 성능에서 큰 차이를 보여줄수도 있다. key값을 통일하여 불필요한 재렌더링을 막아 성능을 최적화 할 수도 있으며 반드시 구분이 필요한 컴포넌트들의 key값을 분리하여 자주 변경되는 컴포넌트 구조의 렌더링을 최적화 해 줄 수도 있다. 이러한 과정은 같은 부모요소를 공유할때에 적용된다. 부모요소가 다르다면 key값과 상관없이 다른 컴포넌트로 취급된다.

 

map함수 등을 사용해 하나의 컴포넌트를 여러개의 변수, props등의 차이만 두고 렌더링할때 이 key값의 중요성이 더욱 두드러진다.

 

아래 React 공식문서의 Preserving and Resetting State 항목을 참고하면 key값의 필요성과 활용방안에 대해 자세히 익힐 수 있다.

 

Preserving and Resetting State – React

 

Preserving and Resetting State – React

The library for web and native user interfaces

react.dev

 

 

 

2. key값의 용례 : 라이브러리에서의 컴포넌트 구분

React 자체적으로도 key값은 매우 중요하지만 key값을 주요 매개로 하여 동작하는 라이브러리들이 있다. 각 컴포넌트의  추가/변경/제거를 정확히 추적하여 애니메이션을 만들어주는 framer-motion이나 React Transition Group같은 리액트의 라이브러리가 대표적이다.

 

2-1. key값의 단순 구분

같은 부모요소인 div 내에서 index만을 key값으로 활용하면 구분이 어렵다.

 

같은 div 부모요소에 MedicalInfoBox라는 컴포넌트가 생성되고 있다. data와 filteredData로 각각 다른 배열을 사용하고있지만 index값으로만 key를 지정해주면 컴포넌트들끼리의 구분이 제대로 되지않아 렌더링 최적화와 framer-motion의 애니메이션 작동에 문제가 발생할 수 있다.

 

2-2. 같은 부모요소 내에서의 key값 구분

특정 문자열을 추가하여 구분이 가능하도록 함.

 

서로 다른 배열을 통해 생성된 컴포넌트에 각각의 배열명을 key값 앞에 추가해주면 구분이 가능하도록 할 수 있다. 하지만 이 방식은 렌더링 되는 컴포넌트가 필터링 되어 변경될때 문제가 발생할 수 있다. 예를들어 100개가 표시되던 컴포넌트들이 필터링에 의해 30개로 줄어든다면 기존에 존재하던 컴포넌트와 상관없이 모두 key값이 재배정되므로 필터링이 진행될때마다 재렌더링을 한다는 문제가 발생한다.

 

2-3. 컴포넌트 내용물을 활용한 고유 key값 지정하기

각 컴포넌트의 value값을 활용하여 필터결과에 상관없이 고유한 key값을 가진다.

 

각 컴포넌트의 고유한 내용을 key값으로 활용하면 컴포넌트들이 별개의 고유값을 가지게된다. 이 경우 필터링과정에 의해 렌더링 할 컴포넌트가 줄거나 늘어도 기존에 존재하던 컴포넌트는 재렌더링 할 필요없이 유지할 수 있게되어 렌더링 성능의 최적화가 가능하다.

 

 

 

3. 결론

React의 렌더링 관련 문제와 더불어 각종 라이브러리 사용에서도 중요한 key값은 결과적으로 컴포넌트끼리의 구분 이라는 하나의 목적을 가지고 있다. 구분이 필요할때는 다른 key값으로, 같은 컴포넌트로 취급되길 원할때는 같은 key값으로 지정해주면 되는것이다.

이는 여러 선택지에대한 사용자의 입력값을 구분하여 받거나 배열을 통해 같은 컴포넌트를 여러번 생성해야하는 등의 특정 상황에 아주 큰 차이를 발생 시킨다.

 

상황에 맞는 key값 활용법을 익히면 성능최적화와 라이브러리 사용에 큰 도움을 받을 수 있다.