리액트 - Key Props

정진우·2022년 5월 26일
0

REACT

목록 보기
1/2
post-thumbnail
function Main = () => {
  ...생략
  const [commentList, setCommentList] = useState([]);
  
  const postComment = () => {
      setCommentList([...commentList, { username: 'test', comment: commentValue}]);
      setCommentValue('');
      setIsValid(false);
  }
  ...생략
    return (
       ...생략
      {commentList.map((comment) => {
          return (
           <Comment 
             username={comment.username} 
             comment={comment.comment}
            />
         )
      })}
      ...생략
    )
}

위의 코드는 map 함수를 이용해 댓글을 렌더링하는 코드이다. Comment 컴포넌트에서 username과 comment를 사용하기 위해 props로 넘겨주었다. 하지만, 아래와 같은 Warning이 발생한다.


해석하면, 리스트 안의 각각의 자식 요소는 고유한 key prop을 가져야 한다는 뜻이다.


Comment 컴포넌트 부분 코드를 아래와 같이 변경하면 Warning이 사라진다.

{commentList.map((comment, idx) => {
  return (
    <Comment
      key={idx}
      username={comment.username} 
      comment={comment.comment}
    />
  )
})}

그렇다면, key prop은 왜 필요할까?



🔑 Key

Key는 React가 어떤 항목을 변경, 추가 또는 삭제할지 식별하는 것을 도와준다.
Key는 엘리먼트에 안정적인 고유성을 부여하기 위해 배열 내부의 엘리먼트에 지정해야 한다.
Key를 선택하는 가장 좋은 방법은 해당 항목을 고유하게 식별할 수 있는 문자열을 사용하는 것이다.
대부분의 경우 데이터의 ID를 Key로 사용하고, 내가 사용했었던 방법인 idx(인덱스)를
Key로 사용하는 방법은 권장되지 않는다. 항목의 순서가 바뀔 수 있는 경우 성능이 저하되거나
컴포넌트의 state와 관련된 문제가 발생할 수 있기 떄문이다.



🖌 render

Key에 대해 더 알아보기 전에 render 함수에 대한 이해가 필요하다.
render 함수는 React 엘리먼트 트리를 만들어 준다.
state나 props가 갱신되면 render() 함수는 새로운 React 엘리먼트 트리를 반환한다.
이때 React는 방금 만들어진 트리에 맞게 가장 효과적으로 UI를 갱신하는 방법을 알아낼 필요가 있다.


하나의 트리를 다른 트리로 변환하기 위한 최소한의 연산 수를 구하는 알고리즘은
n개의 엘리먼트가 있는 트리에 대해 O(n³)의 복잡도를 가진다.


하지만, React는 위의 알고리즘 대신에 두 가지 가정을 기반하여
O(n) 복잡도의 휴리스틱 알고리즘을 구현했다.

  1. 서로 다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.
  2. 개발자가 key prop을 통해, 여러 렌더링 사이에서
    어떤 자식 엘리먼트가 변경되지 않아야 할지 표시해 줄 수 있다.



자식에 대한 재귀적 처리

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

예를 들어, 자식의 끝에 엘리먼트를 추가하면, 두 트리 사이의 변경은 잘 작동할 것이다.

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

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

React는 두 트리에서 <li>first</li>가 일치하는 것을 확인하고,
<li>second</li>가 일치하는 것을 확인한다.
그리고 마지막으로 <li>third</li>를 트리에 추가한다.


하지만 위와 같이 단순하게 구현하면, 리스트의 맨 앞에 엘리먼트를 추가하는 경우
성능이 좋지 않다.

<ul>
  <li>위코드</li>
  <li>저스트코드</li>
</ul>

<ul>
  <li>5기</li>
  <li>위코드</li>
  <li>저스트코드</li>
</ul>

React는 <li>위코드</li><li>저스트코드</li> 종속 트리를
그래도 유지하는 대신 모든 자식을 변경하고 이것은 비효율적이며
문제가 될 수 있다.



✨ Key를 사용하는 이유

위와 같은 문제를 해결하기 위해, React는 Key 속성을 지원한다.
자식들이 Key를 가지고 있다면, React는 Key를 통해 기존 트리와
이후 트리의 자식들이 일치하는지 확인한다.
예를 들어, 위 비효율적인 예시에 Key를 추가하여 트리의 변환 작업이
효율적으로 수행되도록 수정할 수 있다.

<ul>
  <li key="2015">위코드</li>
  <li key="2016">저스트코드</li>
</ul>

<ul>
  <li key="2014">5기</li>
  <li key="2015">위코드</li>
  <li key="2016">저스트코드</li>
</ul>

이제 React는 '2014' key를 가진 엘리먼트가 새로 추가되었고,
'2015'와 '2016' key를 가진 엘리먼트는 그저 이동만 하면 되는 것을 알 수 있다.


실제로는 key에 엘리먼트의 식별자(ex. id값) 그대로 해당 데이터 key로 사용할 수 있다.

<li key={user.id}>{user.name}</li>

하지만 엘리먼트 식별자가 없다면, 데이터 구조에 ID라는 속성을 추가해 주거나
데이터 일부에 해시를 적용해서 key를 생성할 수 있다.
해당 key는 오로지 형제 사이에서만 유일하면 되고, 전역에서 유일할 필요는 없다.


위에서 언급했던 것처럼 최후의 수단으로 배열의 인덱스를 key로 사용할 수 있다.
항목들이 재배열되지 않는다면 이 방법도 잘 동작하지만, 재배열되는 경우 비효율적으로
동작한다.

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

profile
프론트엔드 개발자를 꿈꾸는

0개의 댓글