React는 선언적 API
를 제공하기 때문에 갱신이 될 때마다 무엇이 바뀌었는지 고려할 필요가 없습니다.
리액트에서는 내부적으로 비교(diffing) 알고리즘으로 시간 복잡도 O(N)
을 가진 휴리스틱(heuristic) 알고리즘
을 구현해서 적용합니다.
따라서 리액트는 비교 알고리즘을 통해 컴포넌트의 업데이트를 예측 가능
하게 만들고 빠른 앱을 만들 수 있습니다.
잠깐! 선언적 API란?
우리가 리액트에게 원하는 UI의 상태를 알려주면(선언적), 그 상태를 기반으로 DOM이 자동적으로 업데이트가 되는 것입니다.
예: JSX 문법을 기반으로 컴포넌트를 작성하면 렌더링은 React에서 알아서 처리해줍니다.
두 가지 가정
을 기반으로 휴리스틱 알고리즘을 구현하였습니다.1. Two elements of differnt types will produce different trees.
: 서로 다른 타입을 가진 두 엘리먼트는 다른 트리를 만들어 낸다.
2. The developer can hit at which child elements may be stable across different renders with a key prop.
: 개발자가 key prop를 통해 자식 엘리먼트의 변경 여부를 표시할 수 있다.
React에서 state나 props가 갱신되면 render()
함수가 호출되어 새로운 엘리먼트(Virtual DOM) 트리를 반환
합니다.
이때, 효과적으로 UI를 갱신하기 위해서 기존의 오래된 엘리먼트(Old
Virtual DOM) 트리와 새로운 엘리먼트(New
Virtual DOM) 트리를 비교해 차이점을 찾아내고, 변경된 부분만 실제 돔(Real DOM)
에 반영합니다.
즉, Virtual DOM은 DOM이 변경될 때마다 전체 DOM을 Reflow하는 것이 아니라 비교를 통해
한번만 Reflow를 수행
함으로써 부하를 줄입니다.
// div와 p는 타입이 다르기 때문에 div가 제거되고 p태그와 그 하위 엘리먼트가 추가됩니다.
<div>
<Counter />
</div>
<p>
<Counter />
</p>
div, p
)이 다르면, 리액트는 이전 트리를 완전히 버리고 새로운 트리를 구축
합니다.componentWillUnmount()
가 실행됩니다.componentWillMount()
가 실행되고, componentDidMount()
가 이어서 실행됩니다.* componentWillUnmount() : DOM 노드 파괴될 때 실행.
* componentDidMount() : 새로운 DOM 노드가 삽입된 후에 실행.
componentWillMount() : 새로운 DOM 노드가 삽입되기 전에 실행.
서버사이드 환경
에서 호출하는 용도로 사용했었으며, 더 이상 필요없게 되어 리액트 v16.3
에서 deprecated 되었습니다.속성(Attribute)
을 확인하여, 동일한 속성은 유지하고 변경된 속성들만 갱신합니다.// className 변경
<div className="before" title="stuff" />
<div className="after" title="stuff" />
className
만 수정합니다.// style 속성의 color 변경
<div style={{color: 'red', fontWeight: 'bold'}} />
<div style={{color: 'green', fontWeight: 'bold'}} />
color
속성만 수정하였습니다.해당 노드의 자식들을 재귀적으로 처리합니다.
componentWillReceiveProps
, componentWillUpdate
, componentDidUpdate
를 호출합니다.* componentDidUpdate() : 컴포넌트가 업데이트된 후 호출
* render() 메소드 호출 후 이전과 비교하여 재귀적으로 처리
componentWillReceiveProps() : props 갱신 전 호출
componentWillUpdate() : 컴포넌트가 업데이트되기 전 호출
재귀적
으로 처리할 때, React는 기본적으로 두 리스트를 순차적으로 순회하고 차이점이 있으면 변경합니다.// 효율적 예시
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
<li>first</li>
가 일치하는 것을 확인하고, <li>second</li>
가 일치하는 것을 확인합니다. 그리고 마지막으로 <li>third</li>
를 트리에 추가합니다.맨 앞에 엘리먼트를 추가
하는 경우에는 성능이 좋지 않습니다.
// 비 효율적 예시
<ul>
<li>Duke</li>
<li>Villanova</li>
</ul>
<ul>
<li>Connecticut</li>
<li>Duke</li>
<li>Villanova</li>
</ul>
<li>Duke</li>
와 <li>Villanova</li>
종속 트리를 그대로 유지하는 대신 모든 자식을 변경합니다. 이러한 비효율은 문제가 될 수 있습니다.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>
2014
key를 가진 엘리먼트가 새로 추가되었고, 2015
, 2016
key를 가진 엘리먼트들은 그저 이동만 하면 됩니다.unique
한 값을 넣어줘야 합니다. 최후의 수단으로 index를 넣어 줄 수 있지만 재배열되는 경우엔 비효율적으로 동작하게 됩니다.