[React 렌더링을 알아보자 2편]React 렌더링

TwentyFiveSeven·2021년 2월 4일
4

아직 React의 특징을 모른다면 여기로 가서 먼저 학습해주세요.

React 컴포넌트는 어떻게 렌더링 되지 ??

앞서 봤던 컴포넌트들이 Virtual DOM으로 렌더링 되는 방법은 ReactDOM 라이브러리의 render 메소드를 통해 가능합니다.

function Table({ rows }) { /* ... */ } // 컴포넌트 정의

// 컴포넌트 렌더링
ReactDOM.render(
  React.createElement(Table, { rows: rows }), // "creating" a component
  document.getElementById('#root') // inserting it on a page
);

ReactDOM.render함수가 호출되면, React.createElement 함수가 호출되어 앞서 봤던 Table 컴포넌트를 아래와 같은 객체로 반환합니다.

{
  type: Table,
  props: {
    rows: rows
  },
  ...
}

이 객체가 바로 Virtual DOM을 구성하는 것들 입니다.

만약 자식 컴포넌트를 갖고 있다면

{
  type: 'div',
  props: {
    className: 'Class',
    children: [
      'First',
      'Second'
    ]
  }
}

와 같이 props의 속성으로 children 배열을 갖고있게 됩니다.

이처럼 ReactDOM 라이브러리는 render 메소드를 통해 컴포넌트 객체를 생성하며, 이러한 객체들이 모여 Virtual DOM을 렌더링하는 것 입니다.

React Re-Rendering

앞서 Virtual DOM을 설명할 때 리렌더링하게 되면 어떤 변화가 있었는지 설명을 드렸습니다.
이번에는 좀더 자세히 어떻게 리렌더링이 일어나는지 알려드리겠습니다.

// 컴포넌트 렌더링
ReactDOM.render(
  React.createElement(Table, { rows: rows }), // "creating" a component
  document.getElementById('#root') // inserting it on a page
);

앞서 봤던 예제를 사용해보겠습니다.

위 코드가 처음 렌더링 될 때는 모든 부분을 렌더링하게 됩니다.
하지만 두번째 렌더링부터는 이전 Virtual DOM과 현재 Virtual DOM의 차이점을 비교하여 변화된 부분만을 DOM에 렌더링하게 됩니다.

그렇다면 차이점을 어떻게 비교하는 것일까 ?!

React Document에서는 Reconciliation(재조정)과정을 통해 두 Virtual DOM의 비교가 이뤄지고, DOM이 다시 렌더링 된다고 말해줍니다.

Reconciliation은 어떻게 이뤄질까 ?

React에서는 하나의 트리를 가지고 다른 트리로 변환하기 위한 최소한의 연산 수를 구하는 알고리즘으로 n개의 엘리먼트 트리를 비교하려면 O(n^3)의 시간복잡도를 갖게 될것이고 만약 1000개의 엘리먼트를 비교한다면 100억이라는 많은 시간이 필요하다고 말합니다.

React에서는 이러한 많은 연산을 피하기 위해서 두가지의 가정을 가지고 휴리스틱하게 접근하여 O(n)의 시간복잡도를 갖는 알고리즘을 사용했다고 말합니다.

두가지의 가정은 무엇일까 ?

두가지 가정은 다음과 같습니다

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

1. 서로다른 타입의 두 엘리먼트는 서로 다른 트리를 만들어낸다.

말로만 보면 무슨소리인지 잘 모르시겠죠?

// 업데이트 전:
{ type: 'div', props: { className: 'first' } }

// 업데이트 후:
{ type: 'span', props: { className: 'first' } }

위 코드를 보게되면 type이 변화했습니다.
type이 바뀌게 되면 예전 요소인 해당 엘리먼트부터 자식 엘리먼트 모두를 삭제하고, 더이상의 비교가 필요없기 때문에 바뀐 엘리먼트로 새로 만들어줍니다.

그렇다면 type은 그대로고 props가 바뀌게 되면 어떻게 될까요 ?

// 업데이트 전:
{ type: 'div', props: { className: 'first' } }

// 업데이트 후:
{ type: 'div', props: { className: 'second' } }

위 코드를 보게되면 type은 그대로도 props가 바뀌었습니다.
이 때는 type이 그대로이기 때문에 해당 엘리먼트를 지우지않고, DOM API를 호출하여 props 값을 변경해줍니다.

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

사실 저는 이부분이 더 이해가 되지 않았습니다. 같이 한번 봐볼까요?

// ...
props: {
  children: [
      { type: 'div' },
      { type: 'span' },
  ]
},
// ...
// ...
props: {
  children: [
      { type: 'br' },
      { type: 'div' },
      { type: 'span' },
  ]
},
// ...

위 코드와 아래 코드의 차이점을 보게되면 'div', 'span' 태그의 순서는 그대로지만, 'br'태그가 새로 생성된 것을 볼 수 있습니다.

우리가 생각했을 때는 앞에 추가 하면 될 것 같지만 React는 index를 비교하여 첫번째 index가 'div' -> 'br'로 바뀌었기 때문에 이전 엘리멘트를 삭제하고 다시 만들게 되고, 나머지 엘리멘트들 또한 같은 일을 반복하게 됩니다.

앞에 하나만 추가해주면 되는 것을 상당히 비효율적으로 렌더링이 이뤄지고 있다는 것을 알 수 있습니다.

이를 해결하기 위해서 React에서는 구별을 위한 고유한 key값을 넣어줍니다.
이 때 key값으로 다시 index를 넣게 되면 위에서 발생했던 문제가 똑같이 발생하기 떄문에, 해당 엘리멘트만 갖고있는 고유한 값을 넣어주어야 합니다.

// ...
props: {
  children: [ 
    { type: 'br', key: 'br' },
    { type: 'div', key: 'div' },
    { type: 'span', key: 'span' },
  ]
},
// ...

위 코드처럼 key값을 활용하면 기존에 존재했던 엘리멘트들은 위치만 바꿔주고, 새로 생성된 엘리멘트만 추가해주도록합니다.

이 두가지의 가정으로 React는 휴리스틱한 알고리즘을 적용하는겁니다.

참고
1. https://www.holaxprogramming.com/2018/04/15/react-optimizing-virtual-dom-explained/
2. https://ko.reactjs.org/docs/reconciliation.html
3. https://velog.io/@sbinha/React%EC%97%90%EC%84%9C-Virtual-DOM

이미지출처: https://evilmartians.com/chronicles/optimizing-react-virtual-dom-explained

profile
부지런한 웹개발자🌙

0개의 댓글