본문 바로가기
FE

리액트 - 재조정 (Reconciliation) with (구)공식문서

by owonie 2023. 5. 12.

머리말

포스트 내용은 구버전 리액트 공식문서를 참조하며 작성된 내용이다. 리액트의 DOM 렌더링 방식에 대해서 공부하는 시간을 가져보도록 한다.

갱생하는 리액트 (Reconciliation)

React는 디핑알고리즘(diffing)을 이용해 컴포넌트의 갱신을 효율적으로 구현했다. state 또는 props 가 갱신될 때 마다 render()함수는 새로운 element를 렌더링 해주는데, render 부분을 보면 전체적인 element 트리를 return 해주는 것을 볼 수 있다.

하나의 트리에서 다른 트리로 변환하기 위한 최소한의 연산은 O(n3)의 시간복잡도를 갖는데, 이는 천개의 자식노드가 존재할 때 최대 십억번의 연산을 마쳐야된다는 의미다. 브라우저에서 이런 알고리즘 동작을 너무나도 비효율적인 방식이기에 React에선 두 가지 가정을 기반하여 O(n) 복잡도의 휴리스틱 알고리즘을 구현했다. 

 

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

그래서 비교 어떻게 하는건데 (Diffing Algorithm)

리액트에서 두 개의 노드트리를 비교할 땐 루트(root) 엘리먼트를 비교하고, 그 후의 비교는 루트의 엘리먼트 타입에 따라 달라진다. 비교를 위한 탐색 또한 매우 효율적으로 구현되어있다는 것을 알 수 있는데, 두 트리의 엘리먼트 타입의 동일 여부에 따라 비교 알고리즘이 달라진다. 

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

비교 대상인 두 루트의 엘리먼트의 타입이 다르면 React는 이전 트리를 버리고 완전히 새로운 트리를 구축한다. 이렇게 구현한 경우엔 불필요한 자식노드를 방문할 필요가 없어지기에 시간복잡도를 대폭 줄일 수 있다. 

여기서 헷갈릴 수 있어서 하나 집고 넘어가자면, 리액트의 엘리먼트(노드)는 자바스크립트 객체 형식으로 구현되어있다. 노드를 생성하고 삭제하는 일은 사실 컴포넌트 인스턴스를 파괴하고(componentWillUnmount()) 생성하는(UNSAFE_componentWillMount() -> componentDidMount())가 실행되는 것 과 마찬가지다. 여기서 UNSAFE_componentWilMount()는 레거시 코드이기에 구버전 기준으로 설명하고 있다는 것을 알아두자.

 

엘리먼트의 타입이 같은 경우

타입이 같을 땐 본격적으로 비교를 시작할 수 있다. 비교하는 방식은 두 엘리먼트의 속성(attribute)을 체크하며 다른 내역들만 속성 갱신을 해준다. 즉 속성 값이 바뀌었다고 노트를 갈아끼우지는 않는다는 의미다. 해당 엘리먼트의 비교가 끝나면 자식 노드들을 검사해주고 만약 자식노드 둘의 타입이 다르면 다시 위에 설명했던 타입이 다른 경우 비교 알고리즘을 통해 진행이 된다. 순차적으로 재귀적으로 재조정 과정이 진행된다.

 

같은 타입의 컴포넌트 엘리먼트

컴포넌트가 갱신되면 인스턴스는 동일하게 유지되어 렌더링 간 state가 유지됩니다.

그렇다고 한다. 컴포넌트가 갱신되더라도 인스턴스는 그대로 유지가 되게끔 구현이 되어있다. 새로운 내용을 반영하기위해 새로운 인스턴스를 생성하지 않고 컴포넌트의 state가 유지되어 메모리의 사용을 최소화한다. 타입이 같기에 속성값만(props) 변경해준다고 생각하면 된다. 

 

자식에 대한 재귀적 처리

가상돔을 이용해 두개의 트리를 순회할 때 어떻게 재귀적으로 처리하는지 알려준다. 리액트는 효율적으로 자식노드의 첨삭을 구현하기 위해 Keys라는 방식을 도입했다. React는 key를 통해 같은 트리에 있는 자식노드들 (같은 타입)을 구분짓는 일을 가능케 해준다. 

그렇기에 리스트와 같은 컴포넌트를 사용할 때 key의 설정이 매우 중요하다. 중복되지 않는 유니크한 키 속성을 노드에 넣어줘야 state가 꼬여서 발생하는 사이드 이펙트를 방지할 수 있다.

 

끝말

리액트가 18 버전으로 넘어오면서 beta 문서가 공식문서로 변경되었다. 다시 한번 새롭게 리액트의 바뀐 내용에 대해 설명해주었는데, 여유가 있을 때 최근버전의 재조정 관련 내용으로 포스팅 할 예정이다.

마지막으로 다시한번 더 휴리스틱 알고리즘이 정상작동하기 위한 가정을 적으며 해당 포스트를 마무리하겠다.

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