개요
브라우저의 Javascript 엔진은 단일 스레드로 동작한다. 즉 Javascript는 하나의 스레드에서 모든 로직을 순차적으로 실행한다. 여기에는 UI 업데이트 작업도 포함되어있으며 스레드가 하나이기에 UI 업데이트 작업이 있더라도 앞에 다른 작업이 있다면 UI 업데이트가 중단되는 문제가 생기게 된다. React에서는 이러한 UI 업데이트 작업을 최적화기위해 Fiber라는 개념을 도입하였다.
Fiber구조 에서 가장 중요한 키워드는 동시성 이며 Fiber는 동시성 스케쥴링을 통해 UI 업데이트 작업에 우선순위를 부여하여 더 높은 우선순위의 경우 먼저 처리해주는 방식으로 동작하도록 만들어주는 역할을 한다.
이로 인해 javascript의 UI 업데이트 작업이 더 효율적으로 동작하게 된다.
Fiber 알고리즘
React 내부에서 상태 변화가 일어나면 React는 reconciliation(재조정), 렌더 과정을 거치게된다. 재조정 과정은 virtualDOM의 최신 버전과 현재 화면의 버전을 비교하여 바뀐 부분을 감지하며 렌더 과정에서 이를 반영한다.
그렇다면 이게 왜 동시성이라는 키워드와 연관이 있는것일까?
기존 시스템에서는 위의 재조정, 렌더 과정을 동기적으로 즉 하나의 큰 태스크로 실행하였다. 이 작업은 크면 클수록 이 작업이 끝날때까지 메인 스레드를 점유하게되고 다른 작업을 할 수 없게 만들었다. react는 이를 해결하기 위해 fiber라는 개념을 적용하였다.
fiber는 위 방식의 단점을 보완하기 위한 알고리즘이다. 이를 통해 incremental rendering즉 태스크를 쪼개 우선순위에 따라 태스크를 처리하는 렌더링 방식이 가능하게 되었으며 렌더링 작업을 쪼개 여러 프레임에 걸쳐 렌더링하는 동시성 렌더링이 가능하게 되었다.
Fiber tree
Fiber 트리의 각 노드는 return, sibling, child 값을 사용한 조금 특이한체인 형태로 이루어져 있다.
fiber 트리는 기존의 트리 자료구조처럼 단순하게 깊이 우선 탐색이 아니라 각 노드의 return, sibling, child를 사용해 child 탐색 > sibling 탐색 > return 탐색 > 다음 fiber 노드 탐색 순으로 동작한다. 각 fiber는 다음 fiber를 가리키고 있기에 이 작업이 중간에 멈추더라도 현재의 fiber만 알고있으면 작업 재개가 가능하게 된다.
각 fiber 노드는 변경 사항에 대한 정보를 들고있기에 이를 가지고 있다가 모든 fiber 노드 탐색이 끝나면 마지막 commit 단계에서 DOM에 반영하게된다. 즉 재조정 작업은 commit 이전 상태라면 절대로 화면에 영향을 끼치지 않는다.
fiber노드는 컴포넌트의 input, output에 대한 정보를 들고있다. react의 모든 컴포넌트는 이에 해당하는 fiber노드가 존재하며 다음 fiber노드에 대한 정보, 변경에 대한 정보를 가지고 있다.
Fiber Algorithm
Fiber Algorithm은 이 fiber 구조를 어떤식으로 사용하여 재조정 작업을 진행할까?
Fiber는 재조정 작업을 2단계로 나눠서 실행한다.
- 두 fiber tree를 비교하여 변경점을 수집한다. 이는 동시적으로 동작하며 중단될수도, 다시 가동될수도 있다. react scheduler 내부의 허용 시간동안 작업하고 메인 스레드에 user input, animation과 같은 더 급한 작업을 먼저 실행하기에 이 작업이 아무리 크더라도 메인스레드를 막을 일은 없다. 이 작업의 목적은 변경 정보를 포함하여 새로운 fiber tree를 만들어내는 것이다.
- 첫 번째 단계에서 만든 fiber tree의 변경점들을 모아 실제 DOM에 반영하는 작업을 한다. 즉 Commit 페이즈라고 말한다. 이 작업은 동기적 작업이기에 중단될 수 없다.
이 떄 첫 번째 단계는 동시적으로 작업되고, 두 번째 작업은 동기적으로 작업되기에 첫 번째 단계가 아무리 길게 아무리 여러번 실행되더라도 두 번째 작업이 시작되지 않는다면 화면에 영향을 끼치지 않게된다.
Render, Update
앱이 html 루트에 마운팅 할 때 react는 root라는 이름을 가진 FiberRootNode를 새로 만들고, HostRoot 태그를 가진 fiber 노드를 만들어 root가 HostRoot fiber node를 가리키도록 만든다.
React 앱의 상태가 업데이트되면 현재 root 의 fiber node를 복제하여 workInProgress fiber node로 지정한다.
workInProgress는 말 그대로 현재 처리중이라는 의미이며 이 값이 null이 될 때 까지 작업을 계속 처리한다.
상태 업데이트를 진행할 때 workLoop를 실행하는데 이는 workInProgress가 종료될 때 까지 즉 현재 남아있는 fiber가 없거나, scheduler가 허용하는 시간까지 반복적으로 작업을 실행시켜주는 작업을 한다.
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
workLoop내부 에서는 performUniOfWork 를 실행하는데 이는 남아있는 fiber 노드가 존재하지 않을 때 까지 내부적으로 fiber 노드의 값이 변경되었나를 확인하고 다음 작업 fiber 노드를 workInProgress에 할당해주는 작업을 진행해준다.
이렇게 workLoop 는 다음 fiber 노드가 더 이상 존재하지 않을 때 까지 작업을 반복한 뒤에 종료되며 다 종료된 이후에는 completeUnitOfWork 를 실행시켜준다. 이 때 주의할점은 completeUnitOfWork 를 호출할 때 또 상태가 업데이트되어 처리해야할 fiber 노드가 생겼다면 completeUnitOfWork 를 종료하고 다시 처음부터 실행한다는 것이다.
completeUnitOfWork 는 해당 fiber 노드의 작업을 마무리해주며 해당 fiber 노드의 tag 값에 따른 마무리 동작을 진행한다. 대략적으로 말하면 flag와 lane 정보를 머징하는 작업이다.
이 작업이 마무리 되면 위에서 말한 Fiber 알고리즘의 첫 번째 단계인 재조정 단계가 끝나고 변경된 정보가 포함된 Fiber tree가 생성 완료되게 된다.
이제 두 번째 단계가 시작되는데 이 단계에서는 첫 번째 단계에서 생성된 Fiber tree에 변경 값이 있다면 fiber tree를 flush하며 root가 해당 트리를 가리키도록 변경한다. 이 작업은 동기적 작업이므로 중단될 수 없다.
결론
이는 react 내부적으로 어떤식으로 재조정, 리렌더링 등의 과정을 거치는데에 대한 흐름이며 밖으로 표출되는 내용이 아니기에 자세한 정보를 모르더라도 사실 react로 뭔가를 개발하는데에는 큰 문제가 없다고 생각한다.
이러한 내용들은 나중에 어떠한 업무를 진행하면서 필요한 시기가 오면 그 때 깊게 공부하여 해결해도 전혀 문제가 되지 않는 내용이기에 어떠한 문제가 생겼을 때 이게 뭐가 원인인지, 어떤것을 검색하고 파고들어야 해결할 수 있는지에 대한 키워드를 알고있는 것이 중요하다고 생각한다.
개인적인 생각이지만 react는 언제든지 사용하지 않게 될 수 있는 단순한 라이브러리로 접근하는것이 좋다고 생각하며 react의 메인 언어인 javascript에 대해 아주 깊게 공부하거나 react를 포함하여 일반적인 프로젝트에서의 구조(책임과 역할)에 대해 연구하고 생각을 많이 해보는 것이 더 좋다고 생각한다.
react는 단순한 요리 재료이며 어떤것을 만들게 되더라도 항상 요리사가 중요하다는 마음가짐을 버리지 않는것이 좋을 것 같다.
'frontend' 카테고리의 다른 글
Strict Mode에서 useEffect가 2번 실행되는 이유 (0) | 2024.08.17 |
---|---|
React Lane 간단 정리 (0) | 2024.08.03 |
React19 Support for Document Metadata / stylesheets / async scripts / preloading resources (2) | 2024.06.04 |
두 가지 종류의 공백문자 (0) | 2024.05.23 |
웹 성능 측정에 대한 주관이 많이 들어간 설명(feat.performance api) (0) | 2024.05.08 |