본문 바로가기
2022/개발

재조정 (Reconciliation)

by Tate_Modern 2022. 11. 13.
Reconciliation React 
 

Reconciliation – React

A JavaScript library for building user interfaces

reactjs.org

## Diff Algorithm

### DOM 엘리먼트의 타입이 다를 경우

알고리즘은 이전 트리와 새로 생성한 트리를 비교합니다. 

둘의 엘리먼트 타입이 다르다면 더 이상 비교를 하지 않고 이전 트리를 삭제하고 새로 생성한 트리를 DOM에 반영합니다.

이전에 생성된 트리
새로 생성된 트리

<div> / <span>을 비교 -> div와 span 둘의 엘리먼트 타입이 다름 -> 이전 트리 삭제 -> 새로 생선한 트리 DOM에 반영

 

### DOM 엘리먼트의 타입이 동일할 경우

엘리먼트가 아닌 엘리먼트의 요소가 변경된 것이라면 변경된 요소만을 변경합니다.

/* 이전 트리 */
// <div style={{color: 'red', fontWeight: 'bold'}} />

{
  tag:"div", 
  props: {
     style: {
       color: 'red',
       fontWeight: 'bold'
     },
  children: []
}

/* 새로 생성된 트리 */
// <div style={{color: 'green', fontWeight: 'bold'}} />

{
  tag:"div", 
  props: {
     style: {
       color: 'green',
       fontWeight: 'bold'
     },
  children: []
}

알고리즘은 이전트리와 새로 생성된 트리를 비교합니다.

style의 color가 변경되었다는 것을 감지합니다. 변경된 부분을 DOM에 반영합니다.

(fontWeight는 변경하지 않고 변경된 style의 color 요소만을 업데이트)

DOM 반영 이후 계속해서 비교 알고리즘이 실행됩니다.

이때 엘리먼트 타입이 같다면 트리 전체를 제거하는 것이 아닌 변경된 부분만 업데이트 된다는 것을 알 수 있습니다.

 

<div> / <div> 태그 비교 -> props 비교 변경된 요소 발견 -> 변경된 요소 DOM에 업데이트 -> 자식요소 비교

 

### 컴포넌트 엘리먼트가 같은 타입일 경우

<!-- old -->

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

<!-- new -->

<ul>
  <li>first</li> 
  <li>second</li>
  <li>third</li>
</ul>
(old element, new element)
<ul> , <ul> -> <li>first</li> , <li>first</li>  -> <li>second</li> , <li>second</li> 

1. 변경된 요소를 업데이트하는 과정에서 인스턴스는 이전 상태와 동일하게 됩니다.

(<ul>~<li>second</li> 까지의 요소는 이전 엘리먼트와 새로 생성된 엘리먼트가 같은 타입으로 이전 상태를 유지합니다.)

* 리액트는 이전 컴포넌트와 새롭게 생성된 컴포넌트의 자식요소들을 동시에 비교합니다.

... -> <li>second</li> , <li>second</li>  ->  undefined, <li>third</li> ->  <li>third</li> , <li>third</li>

2. 리액트는 변화가 있는 부분엘리먼트 요소를 새로운 엘리먼트와 맞추기 위해 업데이트 합니다.

그 다음 render 함수를 호출하고 diff alogorithm을 재귀하며 이전 결과와 새로운 결과를 비교합니다.

 

만약 아래와 같이 자식요소가 업데이트 되었다면 어떻게 될까요?

<!-- 이전 트리 -->

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

<!-- 새로운 트리 -->

<ul>
  <li>third</li>
  <li>first</li> 
  <li>second</li>
</ul>
(old element, new element)
<ul> , <ul> -> <li>first</li> , <li>third</li>  

비교하는 과정에서 컴포넌트의 엘리먼트가 달라 이전 트리를 모두 제거하고 새로운 트리를 DOM에 반영합니다.

신규 엘리먼트가 가장 최상단에 보여야 하는 기능이라면 매번 업데이트할 때마다 위의 과정처럼 

새로운 이전 DOM을 삭제하고 신규로 DOM을 생성하게 되면서 성능 문제의 원인이 될 수 있습니다.

 

## key

이러한 이슈를 해결하기 위해서 리액트는 key 값을 통해 엘리먼트를 비교하는 방식을 제공합니다.

각 엘리먼트마다 key를 부여하면 엘리먼트의 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>
// 이전트리
{
 tag: "ul",
 props: null 
 children: [{2015: "Duke"},{2016: "Villanova"}]
}

// 새로운 트리
{
 tag: "ul",
 props: null 
 children: [{2014: "Connecticut"},{2015: "Duke"},{2016: "Villanova"}]
}

비교하는 과정에서 컴포넌트의 엘리먼트가 달라 이전 트리를 모두 제거하고 새로운 트리를 DOM에 반영합니다.

자식요소로 비교하는 것이 아닌 key값으로 비교를 하기 떄문에 이전 이슈를 해결할 수 있습니다.

이 때 key값은 전역적으로 유니크 한 값이 아닌 자식요소들 안에서만 고유하면 됩니다.

(리액트의 예시 코드를 보면 key를 변수로 생성한 후, 각 엘리먼트에 그 값을 부여하는 것을 확인할 수 있습니다.)

 

Create a New Pen

...

codepen.io

###key값 사용 시 주의사항

만약 배열의 index를 key값으로 넣어준다면, 동작이 잘 된다. 하지만 엘리먼트간의 위치 변경이 있게되면 성능이 느려질 수 있다.

재 정렬 역시 성능에 문제를 줄 수 있는 요소이기 때문이다.

만약 배열의 index로 key를 설정하는 경우, 예상한 것과 다르게 동작할 수 있기 떄문에 인덱스로 하는 것은 추천하지 않는다.

 

## Trade off

리액트는 화면에 표시되는 상태의 변경이 일어났을 때 render 함수를 통해 화면을 그립니다.

하지만 간혹 화면에 변화된 것이 없음에도 render 함수가 호출될 수 있다고 합니다. 

(render 함수가 실행되었어도 실제 컴포넌트에는 변경된 값이 없으므로 리렌더는 발생하지 않는다고 합니다.)

 

또한 같은 컴포넌트 내에 있는 형제요소간의 변화가 발생하는 것은 알고리즘이 감지할 수 있지만, 다른 컴포넌트로 이동하는 경우 감지하지 못합니다.

리액트는 컴포넌트 영역 내에서 형제요소에 변경이 일어났기 때문에 리렌더가 발생합니다.

 

이러한 이유는 리액트가 휴리스틱 이론을 기반으로 구성되어 있기 때문이라고 합니다.

 

휴리스틱 이론 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 휴리스틱(heuristics) 또는 발견법(發見法)이란 불충분한 시간이나 정보로 인하여 합리적인 판단을 할 수 없거나, 체계적이면서 합리적인 판단이 굳이 필요하지

ko.wikipedia.org

아래와 같은 룰을 지킨다면 성능 이슈는 없을 거라고 합니다.

1. 리액트의 알고리즘은 타입이 다를 경우, 자식요소에 대한 비교를 시도하지 않습니다.
   상태 변화가 화면에서 발생하는 기능이 있다면, A와 B의 컴포넌트 타입은 동일한 것으로 만드는 것이 좋습니다.

2. key는 유니크하면서 예측가능한 값으로 만드는 것이 좋습니다.
    만약 Math.random과 같이 예측할 수 없는 값으로 key 값을 준다면 불필요한 재생성등의
    이슈가 발생할 수 있기 때문이라고 합니다.

 

'2022 > 개발' 카테고리의 다른 글

0. 레거시  (0) 2022.11.12

댓글