ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Lists & Keys & Reconciliation
    리액트 2021. 8. 29. 23:32

    공부하기 위해 리액트 공식 문서 및 리액트 블로그를 정리한 내용입니다.

     

    리액트 엘리먼트로 리스트를 만들 때 key를 추가하는 것을 깜빡하면 아래와 같은 에러를 확인할 수 있습니다.

    오늘은 왜 key를 추가하지 않으면 왜 이런 경고 문구가 나타나는지, 그리고 리액트한테 key란 무슨 역할을 하는지에 대하여 알아보겠습니다. 

    기본 리스트 컴포넌트

    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <li>{number}</li>
      );
      return (
        <ul>{listItems}</ul>
      );
    }
    
    const numbers = [1, 2, 3, 4, 5];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    );

    NumberList 컴포넌트는 map 함수를 통해 <li>{number}</li>의 배열을 만든 후 const listItems에 삽입 후 최종적으로 <ul>{listItems}</ul> jsx를 생성하여 리턴합니다.

    이를 렌더링하면 아래와 같은 값을 구할 수 있습니다.

    정상적으로 구한 것 같지만 콘솔창을 확인하면 key값이 없다는 에러를 보실 수가 있습니다.

    key를 추가하겠습니다.

    function NumberList(props) {
      const numbers = props.numbers;
      const listItems = numbers.map((number) =>
        <li key={number.toString()}>
          {number}
        </li>
      );
      return (
        <ul>{listItems}</ul>
      );
    }
    
    const numbers = [1, 2, 3, 4, 5];
    ReactDOM.render(
      <NumberList numbers={numbers} />,
      document.getElementById('root')
    );

    재조정(Reconciliation) & Key

    Key는 무엇이기에 리액트는 없어도 작동을 하지만 경고문구를 주는 것일까요?

     

    Key는 리액트의 비교 알고리즘에서 최적화 역할을 합니다. 리액트의 가장 큰 장점은 화면상에 수정이 있을 때 비교 알고리즘을 통해 무엇이 바뀌었는지 파악하고 바뀐 부분만 DOM에 수정하여 반영한다는 점입니다. 서로 다른 두 트리 간을 비교하여 바뀐 부분을 파악하는 알고리즘은 아무리 최첨단의 알고리즘을 사용한다 해도 O(n3)의 복잡도를 가집니다. 

     

    React는 성능 문제를 해결하기 위해, 두 가지 가정을 기반하여 O(n) 복잡도의 휴리스틱 알고리즘을 구현했습니다.

     

    1. 비교하는 두 엘리먼트의 타입이 다르다면, 서로 다른 트리를 만들어낸 것으로 간주한다.
    2. 개발자는 key prop을 사용하여 렌더링을 할 때 어떤 자식 엘리먼트가 변경하지 않는지를 표시할 수 있다.

    두 엘리먼트의 타입이 다른 경우

    <div>
      <Counter />
    </div>
    
    <span>
      <Counter />
    </span>

    두 루트 엘리먼트의 타입이 다르다면 리액트는 그 아래 컴포넌트와 state를 언마운트하고 새롭게 생성합니다. 같은 Counter 컴포넌트지만 루트 엘리먼트 타입이 다르기에 새롭게 생성됩니다.

    두 엘리먼트의 타입이 같을 경우

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

    두 엘리먼트의 타입이 같을 경우 바뀐 속성만 변경을 합니다. 위의 경우 className만 DOM에서 변경됩니다.

     

    두 컴포넌트의 타입이 같을 경우

    만약에 컴포넌트가 갱신되면 state값은 유지되며, 바뀐 element를 반영하여 props를 변경합니다.

     

    자식에 대한 재귀적 처리

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

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

    위의 경우 리액트는 두 트리에서 첫번째 child를 기준으로 서로 비교한 후 맞다면 다음 child로 넘어가여 비교를 합니다. 1st child-1st child 비교, 2nd child-2nd child 비교, 3rd child 추가! 이런 식으로 비교를 한 후 변경된 부분을 찾습니다. 가장 아래 부분만 변경되어 빨리 진행되었지만, 가장 첫번째 부분에 추가를 했다면 알고리즘의 성능은 떨어질 것입니다.

    <ul>
      <li>Duke</li>
      <li>Villanova</li>
    </ul>
    
    <ul>
      <li>Connecticut</li>
      <li>Duke</li>
      <li>Villanova</li>
    </ul>

    위의 예시 경우 맨 첫번째에 새로 추가하여 전에 비해 성능이 떨어집니다. 

    Keys

    성능간의 차이를 극복하기 위해 리액트는 Key를 사용합니다.

    <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의 값을 통해 2014가 새로 추가되었고 2015와 2016의 자리를 이동해야한다는 것을 쉽게 파악할 수 있습니다.

     

    주의점 1

    const todoItems = todos.map((todo, index) =>
      // Only do this if items have no stable IDs
      <li key={index}>
        {todo.text}
      </li>
    );
    • key는 반드시 해당 값에 할당된 고유한 값을 사용해야합니다! 만약에 index를 통해 key값을 준다면 값들간에 뒤엉켜 원하지 않게 DOM에 변경될 수 있습니다.

     

    <ul>
      <li key="0">Duke</li>
      <li key="1">Villanova</li>
    </ul>
    
    <ul>
      <li key="0">Connecticut</li>
      <li key="1">Duke</li>
      <li key="2">Villanova</li>
    </ul>

    예를 들어 index를 사용할 경우, key값은 위와 같이 주어져 key값을 통해 비교하는 리액트에게는 실수를 유발 시킬 수 있습니다!

     

    주의점 2

    • key값은 형제들 사이에서만 고유값이면 됩니다! 전체 범위에서 고유하지 않아도 됩니다!

    '리액트' 카테고리의 다른 글

    Portals  (0) 2021.09.08
    Context  (0) 2021.09.05
    JSX & createElement() & reactDOM.render()  (0) 2021.08.24
    Hooks (useState, useEffect)  (0) 2021.08.23
    컴포넌트 생명주기  (0) 2021.08.22
Designed by Tistory.