https://pomb.us/build-your-own-react/ 을 기반으로 학습 내용을 정리한 글입니다.
STEP 3 - Concurrent Mode
이전 step까지의 코드에서 리팩터링이 필요한 부분이 있다. 그 부분을 먼저 수정하고 진행해 보자.
바로 render function 내부에 재귀로 호출하는 부분이다.
element.props.children.forEach((child) => render(child, dom));
[왜 문제가 될까?🧐]
렌더링을 시작하면 완벽한 element tree를 렌더링 할 때까지 멈추지 않는다. element tree가 크면 메인 스레드를 오랫동안 차단할 수 있다. 그러면 브라우저가 사용자 입력 처리나, 애니메이션을 매끄럽게 유지하는 것과 같은 우선순위가 높은 작업을 해야 하는 경우에도 렌더링이 완료될 때까지 기다려야 한다.
그래서 우리는 작업을 작은 단위로 나누고 각 단위를 마친 후에 수행해야 할 다른 작업이 있으면 브라우저가 렌더링을 중단하도록 할 것이다.
let nextUntilOfWork = null;
function workLoop(deadline) {
let shouldYield = false;
while (nextUntilOfWork && !shouldYield) {
nextUntilOfWork = performUnitOfWork(nextUnitOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
function performUnitOfWork(nextUnitOfWork) {
// TODO
}
위 코드는 브라우저의 유휴 시간을 활용해 작업을 점진적으로 처리하는 방식인 협력적 스케줄링을 구현한 것이다.
즉, 브라우저가 주어진 작업을 한 번에 모두 처리하는 것이 아닌 유휴 시간 동안 조금씩 작업을 처리하여 메인 스레드가 계속해서 응답할 수 있게끔 한다.
위 코드를 이해하기 위해 하나씩 조금 뜯어 살펴보자!
nextUntilOfWork는 다음에 처리해야 할 작업을 가리킨다.
workLoop 함수는 실제 작업 loop를 관리한다.
- deadline 객체는 브라우저가 유휴시간 동안에 이 함수에 얼마나 더 시간을 줄 수 있는지를 의미한다.
- shouldYield는 브라우저가 다른 작업에 리소스를 할당해야 하는지 여부를 결정하는 플래그다.
- while 루프는 nextUntilOfWork가 null이 아니고 shoudYield가 false인 동안 계속 실행된다.
이 루프는 performUnitOfWork에 함수의 현재 작업(nextUntilOfWork)을 전달해 처리하고, 반환된 값은 다음 작업으로 설정된다.
- shouldYield = deadline.timeRemaining() < 1은 브라우저의 유휴 시간이 거의 끝나가는지를 확인하는 조건이다. 남은 시간이 1ms 미만이면 루프를 중단하고 shouldYield를 true로 설정한다.
requestIdleCallback(workLoop) 브라우저가 유휴 상태일 때 workLoop 함수를 호출하도록 예약한다.
-> requestIdleCallback은 deadline 매개변수를 제공해, 브라우저가 다시 제어해야 할 때까지 남은 시간을 확인할 수 있다.
performUnitOfWork 함수는 각 단위 작업을 실제로 수행하는 함수다.
이 함수는 nextUnitOfWork를 입력으로 받아 작업을 처리하고 다음에 수행할 작업을 반환한다.
React에서는 이 부분에서 가상 DOM 트리의 각 노드를 순회하면서 작업을 수행하게 된다.
참고로, React에서는 더 이상 requestIdleCallback을 사용하지 않고, scheduler package를 사용한다.
즉, STEP 3을 완료한 상태의 전체 코드는 아래와 같다.
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) =>
typeof child === "object" ? child : createTextElement(child)
),
},
};
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
function render(element, container) {
const dom =
element.type === "TEXT_ELEMENT"
? document.createTextNode("")
: document.createElement(element.type);
const isProperty = (key) => key !== "children";
Object.keys(element.props)
.filter(isProperty)
.forEach((name) => {
dom[name] = element.props[name];
});
container.appendChild(dom);
}
let nextUntilOfWork = null;
function workLoop(deadline) {
let shouldYield = false;
while (nextUntilOfWork && !shouldYield) {
nextUntilOfWork = performUnitOfWork(nextUntilOfWork);
shouldYield = deadline.timeRemaining() < 1;
}
requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);
function performUnitOfWork(nextUntilOfWork) {
// TODO
}
const Didact = {
createElement,
render,
};
/** @jsx Didact.createElement */
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
);
const container = document.getElementById("root");
Didact.render(element, container);
여기까지 STEP 3 완료!
'FRONT-END > React' 카테고리의 다른 글
나만의 React 만들기(Build your own react) - STEP 5 (0) | 2024.09.06 |
---|---|
나만의 React 만들기(Build your own react) - STEP 4 (1) | 2024.09.05 |
나만의 React 만들기(Build your own react) - STEP 2 (0) | 2024.09.04 |
나만의 React 만들기(Build your own react) - STEP 1 (0) | 2024.09.04 |
나만의 React 만들기(Build your own react) - STEP 0 (2) | 2024.09.03 |
댓글