- Lane은 렌더링에서 동시성을 가져감에 있어 기존의 한계를 해결하기 위해 새롭게 도입된 모델이다
- Lane 이전에는 Expiration Time 모델을 통해서 관리가 되었다
Expiration Time
- Expiration Time의 우선순위에는 상수인 Sync, Idle, Batched와 중요도에 따라 계산된 값이 있다
- 숫자가 클수록 우선순위가 높고, Sync 또는 만료된 Expiration Time이 가장 우선순위가 높다
- 그래서 Sync는 부호 있는 31비트에서 가장 큰 수를 사용한다
// reconciler > ReactFiberExpirationTime.js
const NoWork = 0
const Never = 1
const Idle = 2
const Sync = MAX_SIGNED_31_BIT_INT
const Batched = Sync - 1
- 우선 순위를 기반으로 Expiration Time을 계산하는 방법은 렌더링 모드에 따라 다음과 같ㅇ ㅣ결정된다
// reconciler > ReactFiberWorkLoop.js > computeExpirationForFiber()
function computeExpirationForFiber(currentTime, fiber) {
const mode = fiber.mode;
if ((mode & BlockingMode) === NoMode) {
return Sync;
}
const priorityLevel = getCurrentPriorityLevel(); // 스케쥴러에서 이벤트의 우선순위를 참조한다
if ((mode & ConcurrentMode) === NoMode) {
return priorityLevel === ImmediatePriority ? Sync : Batched;
}
switch (priorityLevel) {
case ImmediatePriority:
expirationTime = Sync;
break;
case UserBlockingPriority:
expirationTime = computeInteractiveExpiration(currentTime);
break;
case NormalPriority:
case LowPriority:
expirationTime = computeInteractiveExpiration(currentTime);
break;
case IdlePriority:
expirationTime = Idle;
break;
default :
invariant(false, 'Expected a valid priority level');
}
}
- 리액트 17의 Adopting Concurrent Mode 문서를 참고하면 모드에는 Legacy Mode, Blocking Mode, Concurrent Mode가 있으며, 코드를 보면 알겠지만 정식 버전으로는 switch 문에 도달할 수 없다
- 우선순위 기반 렌더링이나 동시성 렌더링 기능을 사용하기 위해서는 실험적 버전을 통해 리액트를 설치해야 한다
- 마지막으로 Expiration Time에서는 렌더링 대상의 우선순위를 기준으로 같거나 큰 업데이트만 렌더링 대상에 포함되어 배치처리 된다
- 다음은 렌더링 과정에서 현재 위치의 서브 트리가 렌더링 대상에 포함되는지 확인하는 코드이다
// reconciler > ReactFiberBeginWork.js > balloutOnAlreadyFinishedWork()
const childExpirationTime = workInProgress.childExpirationTime
if (childExpirationTime < renderExpirationTime) {
return null
} else {
cloneChildFibers(current, workInProgress)
return workInProgress.child
}
- 서브 트리의 우선순위(childExpirationTime)가 현재 렌더링 중인 우선순위(renderExpirationTime)보다 낮다면 null을 반환하여 하위 트리로 렌더링 작업이 진행되지 않도록 한다
Lane 모델이 해결하고자 하는 것
업데이트 개념 분리
- Expiration Time은 두 가지 개념이 하나의 시간 데이터에 존재했다
- 우선순위
- 업데이트를 발생시킨 이벤트를 기준으로 우선순위를 결정하고 업데이트 간 우선순위는 대소 비교를 통해 판단
- 배치 여부
- 업데이트의 배치 여부는 값의 대소 비교를 통해 판단
- 이런 구현사항은 상위 우선순위를 가진 업데이트를 먼저 처리하도록 설계되어 있어 문제 없이 잘 동작하고 있었다
- A > B > C의 우선순위가 주어지면 A에 대한 작업 완료 없이는 B에 대한 작업을 수행할 수 없다. 마찬가지로 A와 B를 모두 완료하지 않고 C로 넘어갈 수 없다
- 이는 Suspense가 나오기 이전에 설계되었고, 우선순위가 아닌 다른 이유로 작업의 순서를 결정해야 할 이유가 없었다
- 하지만 Suspense와 함께 렌더링에 IO-Bound라는 개념이 추가되면서 이야기가 달라졌다