리액트 초기 마운트 살펴보기

1달 전

리액트는 Trigger, Render, Commit 이 3단계로 초기 마운트를 진행한다.

이 3단계를 자세하게 살펴보겠다. (초기 마운트와 크게 관련이 없는 부분은 생략했다.)


(영상을 먼저 보면 아래 내용을 이해하기 더 쉽다.)


​알면 좋은 사전 지식

​FiberNode

function FiberNode(tag, pendingProps, key, mode) {
  this.tag = tag; // FiberNode의 종류
  this.stateNode = null; // 실제 DOM 노드
  
  // 트리와 같은 구조를 위한 속성
  this.return = null;
  this.child = null;
  this.sibling = null;    
  
  this.pendingProps = pendingProps;  // 현재 렌더링 중인 업데이트에서 사용되는 props값
  this.memoizedProps = null; // 이전 렌더링에서 사용된 props 값  	
  
  this.flags = NoFlags; // 특정 Fiber에서 수행해야 할 작업(커밋 단계에서 사용된다.)
  this.alternate = null; // 이전 버전의 FiberNode
	...
}


​FiberNode의 tag 값

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // 함수(0)인지 클래스(1)인지 결정되기 전
export const HostRoot = 3; // Fiber Tree의 루트 노드
export const HostComponent = 5; // div, p와 같은 실제 DOM 요소
export const HostText = 6;
export const Mode = 8; // StrictMode
...


​FiberTree

이 FiberNode들이 트리와 같은 구조로 되어 있고, 리액트는 이런 트리 구조를 순회하면서 컴포넌트를 렌더링한다. 이 트리 구조를 구성하는 속성은 return, child, sibling 이다.

이미지

return : FiberNode의 부모

child : FIberNode의 자식 (만약 자식이 여러 개일 경우, 첫 번째 자식만 child로 설정한다.)

sibling : FiberNode의 다음 형제


리액트는 DFS 방식으로 로 FiberTree를 순회한다.

// 예시
function traverseFiberTree(fiberNode) {
	if(fiberNode.child !== null) {
		// child로 이동
		traverseFiberTree(fiberNode.child)
		...
	} else if(fiberNode.sibling !== null) {
		traverseFiberTree(fiberNode.sibling)
		...
	} else {
    let parent = fiberNode.return;
    while (parent && !parent.sibling) {
        parent = parent.return;
    }
    if (parent && parent.sibling) {
        traverseFiberTree(parent.sibling);
    }
	}
}


​디버거 도구를 이용한 리액트 내부 구조 분석

https://github.com/facebook/react/tree/v18.2.0/packages/react-reconciler

위와 같이 깃허브의 리액트 소스코드를 보면서 내부 구조를 이해하는 것은 어렵기 때문에,

리액트 프로젝트를 하나 생성한 후, 크롬 개발자 도구에서 디버거 기능을 이용하여 분석했다.


이미지


​디버거의 주요 기능

  • [브레이크 포인트]

    브레이크 포인트로 특정 코드 줄에서 실행을 일시 중지할 수 있다.

    이를 이용하여 어떤 함수가 어떤 매개변수를 받고 어떤 값을 return 하는 지를 쉽게 볼 수 있다.

  • [콜 스택]

    현재 함수가 어떤 함수에 의해 호출되었는지 추적할 수 있다.


  • [디버거의 동작]

    스크립트 실행 계속하기 : 다음 줄 실행

    Step Over(다음 함수 호출) : 해당 함수 혹은 코드가 관련 없다고 생각하면 건너뛴다.

    Step Out(함수 벗어나기) : 현재 함수 실행을 마치고 호출한 함수로 이동한다.



​Trigger 단계

  1. createRoot() : FiberRootNode 와 HostRoot 를 생성하고 연결한다.

  2. .render() : 스케줄러에 렌더 단계를 시작하는 함수를 등록한다.


​1. createRoot()

createRoot(document.getElementById("root")!)

...

// container = div#root, options = undefined
function createRoot(container, options) {
	...
  var root = createContainer(container, ConcurrentRoot, ...);
	...
  return new ReactDOMRoot(root);
}


​createContainer()

function createContainer(containerInfo, tag, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError, transitionCallbacks) {
  ...
  return createFiberRoot(containerInfo, tag, hydrate, initialChildren, hydrationCallbacks, isStrictMode, concurrentUpdatesByDefaultOverride, identifierPrefix, onRecoverableError);
}


​createFiberRoot()

// containerInfo = div#root, tag = 1
function createFiberRoot(containerInfo, tag, ...)
	// FiberRootNode를 생성한다.
  var root = new FiberRootNode(containerInfo, tag, hydrate, identifierPrefix, onRecoverableError);
  
  // HostRoot를 생성한다.
  var uninitializedFiber = createHostRootFiber(tag, isStrictMode);
  
  // FiberRootNode.current = hostRoot
  root.current = uninitializedFiber;
  
  // hostRoot.stateNode = FiberRootNode
  uninitializedFiber.stateNode = root;
  ...
  return root;
}

먼저 생성자 함수인 new FiberRootNode() 로 RootFiberNode인 root를 생성한다. 이 과정에서 RootFiberNode 에 containerInfo 속성에 document.getElementById("root")! 인 div#root 가 할당된다.

다만 RootFiberNode 는 초기 마운트 작업에서는 주로 사용되지는 않는다.


createHostRootFiber() 를 호출하여 HostRoot 를 생성한다. HostRoot 는 FiberTree의 루트 노드이다. FiberTree를 이용한 작업(예: 초기 마운트, 리렌더링)들은 HostRoot 에서 시작하여 HostRoot 에서 끝난다.


마지막으로 FiberRootNode와 HostRoot를 생성하여 둘을 연결한다.

이미지


​2.0 React Element 생성

render() 함수가 실행되기 전에 JSX(<StrictMode><App /></StrictMode>)를 React Element로 변환한다. 아래에 생성된 React Element를 볼 수 있다.

이미지

주의해야 할 점이 있는데, 이 과정에서 모든 계층의 요소들이 한 번에 React Element로 변환되는 것이 아니다.

함수 컴포넌트는 렌더링 단계에서 함수가 실행되어 자식에 대한 React Element를 생성된다.

(사진에서 type: f App() 인 React Element는 props가 빈 객체인 것을 볼 수 있다.)


​2. render(children)

render는 렌더 단계를 시작하는 함수를 스케줄러에 등록하는 함수이다.

function ReactDOMRoot(internalRoot: FiberRoot) {
  this._internalRoot = internalRoot;
}

// children = <StrictMode><App /></StrictMode> -> React Element
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function (children) {
  var root = this._internalRoot;
  ...
  updateContainer(children, root, null, null);
};


​updateContainer(children, container)

// element = React Element, container = RootFiberNode
function updateContainer(element, container, parentComponent, callback) {

	var current$1 = container.current; // HostRoot
	var eventTime = requestEventTime();
  var lane = requestUpdateLane(current$1);
  ...
  var update = createUpdate(eventTime, lane);
  ...
  update.payload = {
    element: element
  };
  ...
	const root = enqueueUpdate(current$1, update, lane) //  RootFiberNode
	...
  if (root !== null) {
    scheduleUpdateOnFiber(root, current$1, lane, eventTime);
		...
  }
	...
}

eventTime과 lane을 설정하고, createUpdate 로 update 객체를 생성하고, update.payload에 element(children) 을 할당한다.


​requestEventTime()

업데이트를 시작한 정확한 시간을 얻는다.

var eventTime = requestEventTime(); // localPerformance.now(); = 2238.5

Date.now()와는 다르게, performance.now() 는 마이크로초 정밀도까지 표현될 수 있는 부동 소수점으로 시간을 나타낸다. 또한 Date.now() 는 시스템 시계에 의존하기 때문에 시스템과 시계 왜곡 등과 같은 사용자의 시계 조정에 영향을 받을 수 있다. 반면 performance.now() 메서드는 현재 시간이 감소하거나 조정되지 않는 모노토닉 시계인 timeOrigin 속성을 기준으로 한다.


​requestUpdateLane()

Lane 은 React 18부터 렌더링 우선순위 관리를 위해 도입된 시스템이다. 즉, 업데이트들을 우선순위에 따라 다른 Lane으로 분리해서 처리한다.

리액트에서 useTransition() 와 useDeferredValue() 를 본 적이 있을텐데 이것이 Lane을 활용한 React API이다.

렌더 단계에서 16이라는 Lane을 자주 볼 수 있는데, 이 Lane은 DefaultLane인 0b10000 이다.

function requestUpdateLane(fiber) {
	...
  var eventLane = getCurrentEventPriority();
  return eventLane;
}

function getCurrentEventPriority() {
  var currentEvent = window.event; // undefined

  if (currentEvent === undefined) {
    return DefaultEventPriority; // 16
  }

  return getEventPriority(currentEvent.type);
}

return eventLane;

... 
export const DefaultEventPriority: EventPriority = DefaultLane;

export const DefaultLane: Lane = 0b0000000000000000000000000010000; // 16


​createUpdate(eventTime, lane);

이 업데이트를 시작한 시간과 Lane을 이용하여 업데이트 객체를 생성한다.

var update = createUpdate(eventTime, lane);

// eventTime = 2238.5, lane = 16
function createUpdate(eventTime, lane) {
  var update = {
    eventTime: eventTime,
    lane: lane,
    tag: UpdateState, // 0
    payload: null,
    callback: null,
    next: null
  };
  return update;
}


​scheduleUpdateOnFiber()

function scheduleUpdateOnFiber(root, fiber, lane, eventTime) {
	...
  if ((executionContext & RenderContext) !== NoLanes && root === workInProgressRoot) {
		...
  } else {
	  ...
    ensureRootIsScheduled(root, eventTime);
		...
  }
}


​ensureRootIsScheduled()

// root: FiberRootNode, 
function ensureRootIsScheduled(root, currentTime) {
	var existingCallbackNode = root.callbackNode; // null
	...
	var newCallbackPriority = getHighestPriorityLane(nextLanes); // 16
	var newCallbackNode;	
	if (newCallbackPriority === SyncLane) {
		...
	} else {
		var schedulerPriorityLevel;

	  switch (lanesToEventPriority(nextLanes)) {
	    ...
	    case DefaultEventPriority:
	      schedulerPriorityLevel = NormalPriority; // 3
	      break;
			...
	  }

    newCallbackNode = scheduleCallback$1(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
	}
}


​scheduleCallback()

var taskQueue = [];

// priorityLevel = 3, callback = f() = performConcurrentWorkOnRoot(root), options = undefined
function scheduleCallback(priorityLevel, callback, options) {
	var currentTime = exports.unstable_now(); // localPerformance.now() = 2238.5
	var timeout;
	 
	switch (priorityLevel) {
		...
	  case NormalPriority:
	  default:
	    timeout = NORMAL_PRIORITY_TIMEOUT; // 5000
	    break;
	}
	
	var expirationTime = startTime + timeout; // 2238.5 + 5000 = 7238.5
	var newTask = {
	  id: taskIdCounter++,
	  callback: callback,
	  priorityLevel: priorityLevel,
	  startTime: startTime,
	  expirationTime: expirationTime,
	  sortIndex: -1
	};
	
	if (startTime > currentTime) {
	  ...
	} else {
	  newTask.sortIndex = expirationTime;
	  push(taskQueue, newTask); 
	  // heap 자료구조로 sortIndex를 오름차순으로 taskQueue를 관리한다.
	  if (!isHostCallbackScheduled && !isPerformingWork) {
	    isHostCallbackScheduled = true;
	    requestHostCallback(flushWork); // flushWork는 렌더 단계를 시작하는 함수이다.
	  }
	}
	
}

requestHostCallback 에 콜백함수로 flushWork 를 전달한다.


​requestHostCallback()

requestHostCallback() 이 실행되기 전에, 아래와 같이 비동기 작업 예약을 위한 세팅이 먼저 되어있다.

var schedulePerformWorkUntilDeadline;

if (typeof localSetImmediate === 'function') {
  // Node.js 와 IE 환경
	...
} else if (typeof MessageChannel !== 'undefined') {
  // DOM and Worker 환경
	// setTimeout의 최소 4ms 문제 때문에 MessageChannel을 선호한다.
  var channel = new MessageChannel(); // 1
  var port = channel.port2; 
  channel.port1.onmessage = performWorkUntilDeadline; // 2

  schedulePerformWorkUntilDeadline = function () { // 3
    port.postMessage(null);
  };
} else {
  ...
}
  1. MessageChannel 객체를 생성하면 포트(port1, port2) 두 개가 생성된다.

  2. 메시지 이벤트 핸들러를 등록한다. port1이 메시지를 받을 때, performWorkUnitlDeadLine 함수가 실행된다.

  3. schedulePerformWorkUntilDeadline가 호출되면, post.postMessage(null) 로 port2 에서 메시지를 보내고, port1 이 이 메시지를 받고, 등록된 핸들러 performWorkUnitlDeadLine 를 실행한다.

MessageChannel의 postMessage는 마이크로태스크 큐에 쌓인다. 즉, Promise.then과 같은 우선순위를 갖고, setTimeout보다 먼저 실행된다.

var scheduledHostCallback = null;

// callback = flushWork;
function requestHostCallback(callback) {
  scheduledHostCallback = callback; // flushWork

  if (!isMessageLoopRunning) {
    isMessageLoopRunning = true;
    schedulePerformWorkUntilDeadline();
  }
}

스케줄러에 함수를 등록하는 과정은 아래와 같다.

  1. requestHostCallback() 을 호출하면 scheduledHostCallback에 flushWork() 함수를 할당하고, schedulePerformWorkUntilDeadline() 를 호출한다.

  2. schedulePerformWorkUntilDeadline() 이 호출되면 port1 에 메시지를 보낸다.

  3. port1이 메시지를 받고, 태스크 큐에 performWorkUntilDeadline()를 추가한다.

  4. 콜스택이 비었으면, 이벤트 루프가 태스크 큐에서 함수를 가져와 performWorkUntilDeadline() 실행.

  5. performWorkUntilDeadline() 에서 scheduledHostCallback를 실행하여 flushWork 함수가 실행된다.


이미지이미지



​Render 단계

이미지

​performWorkUntilDeadline()

Trigger 단계의 마지막에서 비동기적으로 전달한 함수

let scheduledHostCallback; // flushWork(hasTimeRemaining, initialTime)

var performWorkUntilDeadline = function () {
  if (scheduledHostCallback !== null) {
    var currentTime = exports.unstable_now(); // localPerformance.now() = 384003.399...

    startTime = currentTime;
    var hasTimeRemaining = true; 
    var hasMoreWork = true;

    try {
      hasMoreWork = scheduledHostCallback(hasTimeRemaining, currentTime); // flushWork()
    } finally {
      ...
    }
  } else {
    ...
  }
};

scheduledHostCallback 함수(flushWork)를 호출하여 렌더 단계를 시작한다.


​flushWork()

var currentPriorityLevel = NormalPriority; // 3

// hasTimeRemaining = true, initialTime = 384003.399
function flushWork(hasTimeRemaining, initialTime) {
	...
  var previousPriorityLevel = currentPriorityLevel; // 3

  try {
    if (enableProfiling) {
      ...
    } else {
      return workLoop(hasTimeRemaining, initialTime);
    }
  } finally {
    currentTask = null;
    currentPriorityLevel = previousPriorityLevel;
    isPerformingWork = false;
  }
}


​workLoop()

function workLoop(hasTimeRemaining, initialTime) {
  var currentTime = initialTime; // 384003.399...
  advanceTimers(currentTime);
  currentTask = peek(taskQueue); // 아까 scheduleCallback에서 생성한 newTask

  while (currentTask !== null && !(enableSchedulerDebugging )) {
    ...
    var callback = currentTask.callback; // ensureRootIsScheduled()에서 설정한 performConcurrentWorkOnRoot(root)

    if (typeof callback === 'function') {
      currentTask.callback = null;
      currentPriorityLevel = currentTask.priorityLevel; // 3
      
      // 7238 <= 384003.399...
      var didUserCallbackTimeout = currentTask.expirationTime <= currentTime;

			// performConcurrentWorkOnRoot(root)로 진입!
      var continuationCallback = callback(didUserCallbackTimeout);
      ...
    } else {
      ...
    }
		...
  } 
  ...
}


​performConcurrentWorkOnRoot()

여기서 “렌더 단계”를 Concurrent 모드로 할 것인지, Sync 모드로 할 것인지 결정한다.

초기 마운트는 화면에 UI를 먼저 표시하는 게 우선적이므로, Sync 모드로 진행한다.

// root = div#root
function performConcurrentWorkOnRoot(root, didTimeout) {
	...
  currentEventTime = NoTimestamp // -1;
  currentEventTransitionLane = NoLanes // 0;
	...
  var originalCallbackNode = root.callbackNode; // task
	...
	// 16
  var lanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes);
	...
  var shouldTimeSlice = !includesBlockingLane(root, lanes) && !includesExpiredLane(root, lanes) && ( !didTimeout);
  var exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);
	...  
  return null;
}
function includesBlockingLane(root, lanes) {	
  var SyncDefaultLanes = InputContinuousHydrationLane | InputContinuousLane | DefaultHydrationLane | DefaultLane;  
	// 10000 & 11111 -> 10000 !== 0 -> true
  return (lanes & SyncDefaultLanes) !== NoLanes;
}

// DefaultLane은 SyncLane = BlockingLane이다.
// !true -> false && ... -> 최종적으로 false가 된다.
var shouldTimeSlice = !includesBlockingLane(root, lanes) && !includesExpiredLane(root, lanes) && ( !didTimeout);
var exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);


​renderRootSync()

function renderRootSync(root, lanes) {
  var prevExecutionContext = executionContext;
  executionContext |= RenderContext;
	...
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    ...
    prepareFreshStack(root, lanes);
  }
	...
  do {
    try {
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);

  ...
}


​prepareFreshStack()

function prepareFreshStack(root, lanes) {
	...
  workInProgressRoot = root;
  var rootWorkInProgress = createWorkInProgress(root.current, null);
  workInProgress = rootWorkInProgress;
  ...
  return rootWorkInProgress;
}

FiberTree를 위한 첫 시작점인 rootWorkInProgress를 생성한다.

리액트는 workInProgress 를 이용하여 새로운 FiberTree를 만든다. workInProgress는 작업의 단위이자, FiberNode이다.


​createWorkInProgress()

이미지

createRoot()에서 생성한 HostRoot로 새로운 HostRoot인 workInProgress를 생성한다.


function createWorkInProgress(current, pendingProps) {
	// 초기 마운트시에는 이전 FiberTree가 없기 때문에 current.alternate가 null이다.
  var workInProgress = current.alternate; // null

  if (workInProgress === null) {
	  // HostRoot로 workInProgress = FiberNode를 생성한다.
    workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
    ...
    // 새로 만든 workInProgress(새로운 버전의 HostRoot)와 이전 HostRoot(createRoot에서 생성한 HostRoot)를 연결한다.
    workInProgress.alternate = current;
    current.alternate = workInProgress;
  } else {
	  ...
  } 
  
	// 이전 HostRoot의 속성을 이용하여 새로 만든 workInProgress 속성을 할당?한다.
  workInProgress.flags = current.flags & StaticMask;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.sibling = current.sibling;
	...
  {
	  // HostRoot는 tag가 3이다.
    switch (workInProgress.tag) {
      case IndeterminateComponent: // tag: 2
      case FunctionComponent: // tag: 0
      case SimpleMemoComponent: // tag: 15
        workInProgress.type = resolveFunctionForHotReloading(current.type);
        break;

      case ClassComponent: // tag: 1
        workInProgress.type = resolveClassForHotReloading(current.type);
        break;

      case ForwardRef: // tag: 11
        workInProgress.type = resolveForwardRefForHotReloading(current.type);
        break;
    }
  }

  return workInProgress;
} // Used to reuse a Fiber for a second pass.


​workLoopSync()

workLoopSync()는 Fiber 노드를 순회하는 동기적인 루프이다.

workInProgress 가 null이 아닐 때까지 반복하면서 performUnitOfWork() 를 호출한다.

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}


​workInProgress = HostRoot

이미지

현재 workInProgress는 HostRoot이다. performUnitOfWork가 어떻게 처리되는지 확인해보자.

​performUnitOfWork()

function performUnitOfWork(unitOfWork) {
  ...
  var current = unitOfWork.alternate; // createRoot에서 생성한 HostRoot
	...	
  var next;

  if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
    ...
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    ...
  } else {
    ...
  }
	...
}


​beginWork()

function beginWork(current, workInProgress, renderLanes) {
  ...
  if (current !== null) {
    var oldProps = current.memoizedProps; // null
    var newProps = workInProgress.pendingProps; // null

    if (oldProps !== newProps || hasContextChanged() || (
    } else {
			...
      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
				...
      } else {
        didReceiveUpdate = false;
      }
    }
  } else {
    ...
  }

  workInProgress.lanes = NoLanes;

  switch (workInProgress.tag) {
	  ...
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
		...
  }
}


​updateHostRoot()

function updateHostRoot(current, workInProgress, renderLanes) {
  ...
  var nextState = workInProgress.memoizedState; 
  ...
  // <StrictMode><App /></StrictMode> -> React Element 객체
  var nextChildren = nextState.element;	
  if (prevState.isDehydrated) {
	  ...
  } else {
    ...
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  }

  return workInProgress.child;
}


​reconcileChildren()

nextChildren 인 React Element를 이용하여 자식 FiberNode들을 생성한다.

초기 마운트에서는 HostRoot만 current 값을 가지고 있으므로 ( var current = unitOfWork.alternate;) reconcileChildFibers를 호출한다.

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  if (current === null) {
	  workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
	  // 여기로 진입
    workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
  }
}
...
var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);

function ChildReconciler(shouldTrackSideEffects) {...}

shouldTrackSideEffects 값은 placeSingleChild에서 flags를 설정하기 위해 사용된다.


​reconcileChildFibers()

newChild = nextChildren (<StrictMode />에 대한 React Element)는 자식이 1개 이므로 reconcileSingleElement 로 진입한다.

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
    ...
    if (typeof newChild === 'object' && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE: // Symbol(react.element)
	        // reconcileSingleElement()로 진입
          return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
        ...
      }
			...
		}
    ...
  }
	...
}


​reconcileSingleElement()

 function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
		...
    if (element.type === REACT_FRAGMENT_TYPE) {
      ...
    } else {
	    // createFiberFromElement()로 진입
      var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);

      _created4.ref = coerceRef(returnFiber, currentFirstChild, element);
      _created4.return = returnFiber;
      return _created4;
    }
  }


​createFiberFromElement()

function createFiberFromElement(element, mode, lanes) {
	...
  var type = element.type; // Symbol(react.strict_mode)
  var key = element.key; // null
  var pendingProps = element.props; // { children: {...} } -> <App />
  var fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes);
	...
  return fiber;
}


​createFiberFromTypeAndProps()

function createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes) {
  var fiberTag = IndeterminateComponent;
  var resolvedType = type; // Symbol(react.strict_mode)

  if (typeof type === 'function') {
    ...
  } else if (typeof type === 'string') {
    ...
  } else {
    getTag: switch (type) {
      ...
      case REACT_STRICT_MODE_TYPE:
        fiberTag = Mode; // 8
        mode |= StrictLegacyMode; // 11

        if ( (mode & ConcurrentMode) !== NoMode) {
          // Strict effects should never run on legacy roots
          mode |= StrictEffectsMode; // 27
        }

        break;
     ...
  }

  var fiber = createFiber(fiberTag, pendingProps, key, mode);
  fiber.elementType = type; // Symbol(react.strict_mode)
  fiber.type = resolvedType; // Symbol(react.strict_mode)
  fiber.lanes = lanes; // 16
	...
  return fiber;
}
// tag = 8 (StrictMode), pendingProps = <App/>의 React Element, 
var createFiber = function (tag, pendingProps, key, mode) {
  // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
  return new FiberNode(tag, pendingProps, key, mode);
};

createFiber() 함수로 StrictMode에 대한 FiberNode를 생성한다.

이미지


​reconcileSingleElement()

 function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
		...
    if (element.type === REACT_FRAGMENT_TYPE) {
      ...
    } else {
      var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);

			// 여기로 나오게 된다.
      _created4.ref = coerceRef(returnFiber, currentFirstChild, element); // null
      _created4.return = returnFiber; // return(Fiber Tree의 부모)으로 HostRoot를 할당한다.
      return _created4;
    }
  }

트리와 같은 구조를 위해 자식 FiberNode의 return으로 부모 FiberNode(HostRoot)를 할당한다.


​reconcileChildFibers()

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
    ...
    if (typeof newChild === 'object' && newChild !== null) {
      switch (newChild.$$typeof) {
        case REACT_ELEMENT_TYPE: // Symbol(react.element)
	        // placeSingleChild로 진입
          return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
        ...
      }

      ...
    }
    ...
  
  return reconcileChildFibers;
}


​placeSingleChild()

이미지

HostRoot만 shouldTrackSideEffects 이 true이므로 HostRoot의 자식(StrictMode)의 flags 값이 Placement로 설정된다.

이는 나중에 커밋 단계에서 containerInfo를 연결하기 위해 설정된다.

function placeSingleChild(newFiber) {
  if (shouldTrackSideEffects && newFiber.alternate === null) {
    newFiber.flags |= Placement;
  }

  return newFiber;
}
export type Flags = number;

export const NoFlags = /*                      */ 0b00000000000000000000000000;
export const PerformedWork = /*                */ 0b00000000000000000000000001;

export const Placement = /*                    */ 0b00000000000000000000000010;
export const Update = /*                       */ 0b00000000000000000000000100;
export const Deletion = /*                     */ 0b00000000000000000000001000;
export const ChildDeletion = /*                */ 0b00000000000000000000010000;
...


​performUnitOfWork()

function performUnitOfWork(unitOfWork) {
  ...
  if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    // next에 StrictMode FiberNode를 할당한다.
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    ...
  }
  
  if (next === null) {
    ...
  } else {
    workInProgress = next;
  }
  ...
}

workInProgress = next = StrictMode에 대한 FiberNode를 한다.

workLoopSync() 에서 workInProgress ≠ null이므로 StrictMode에 대한 FiberNode로 performUnitOfWork()를 다시 호출한다.


​workInProgress = StrictMode

이미지

​performUnitOfWork()

function performUnitOfWork(unitOfWork) {
  var current = unitOfWork.alternate; // null
  setCurrentFiber(unitOfWork);
  var next;

  if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    
    // beginWork로 진입
    next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    ...
  }

  resetCurrentFiber();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
    // If this doesn't spawn new work, complete the current work.ㄹ
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner$2.current = null;
}


​beginWork()

// current = null, workInProgress = ReactStrictMode FiberNode, renderLanes = 16
function beginWork(current, workInProgress, renderLanes) {
  ...
  if (current !== null) {
	  ...
  } else {
    didReceiveUpdate = false;
	}
  ...
  switch (workInProgress.tag) {
	   ...
    case Mode:
      return updateMode(current, workInProgress, renderLanes);
		...
  }  
}

HostRoot에서와는 다르게 beginWork에서 updateMode()를 호출한다.


​updateMode()

function updateMode(current, workInProgress, renderLanes) {
	// <App />의 React Element
  var nextChildren = workInProgress.pendingProps.children; 
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

updateMode에서도 동일하게 reconcileChildren 을 호출한다.


​reconcileChildren()

function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  if (current === null) {
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
	...
}


​reconcileChildFibers()

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
	...
  if (typeof newChild === 'object' && newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE:
	      // reconcileSingleElement로 진입
        return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
			...
    }
    ...
  }

	...
}


​reconcileSingleElement()

function reconcileSingleElement(
  returnFiber,
  currentFirstChild,
  element,
  lanes
) {
	var key = element.key;
  var child = currentFirstChild;
	...
  if (element.type === REACT_FRAGMENT_TYPE) {
		...
  } else {
    var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);

    _created4.ref = coerceRef(returnFiber, currentFirstChild, element);
    _created4.return = returnFiber;
    return _created4;
  }
}


​createFiberFromElement()

function createFiberFromElement(element, mode, lanes) {
	...
  var type = element.type; // f App()
  var key = element.key;
  var pendingProps = element.props;
  var fiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes);

  {
    fiber._debugSource = element._source;
    fiber._debugOwner = element._owner;
  }

  return fiber;
}


​createFiberFromTypeAndProps()

function createFiberFromTypeAndProps(type, // React$ElementType
key, pendingProps, owner, mode, lanes) {
  var fiberTag = IndeterminateComponent; // 2
  ...
	// tag = 2(IndeterminateComponent), pendingProps = {}, key = null, mode= 27
  var fiber = createFiber(fiberTag, pendingProps, key, mode);
  fiber.elementType = type; // f App()
  fiber.type = resolvedType; // f App()
  fiber.lanes = lanes; // lane = 16

  return fiber;
}

자식인 <App/> 에 대한 FiberNode가 생성된다.

이미지


​reconcileSingleElement()

function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
   ...
  if (element.type === REACT_FRAGMENT_TYPE) {
    ...
  } else {
    var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);

		// 여기로 빠져나옴
    _created4.ref = coerceRef(returnFiber, currentFirstChild, element); // null
    _created4.return = returnFiber; // react.strictMode FiberNode
    return _created4;
  }
}


​reconcileChildFibers()

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
	...
  if (typeof newChild === 'object' && newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE:
	      // placeSingleChild로 진입
        return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
			...
    }
    ...
  }

	...
}


​placeSingleChild()

function placeSingleChild(newFiber) {
  if (shouldTrackSideEffects && newFiber.alternate === null) {
    newFiber.flags |= Placement;
  }

  return newFiber;
}

shouldTrackSideEffects 이 false이므로 flags가 설정되지 않는다.


​performUnitOfWork()

function performUnitOfWork(unitOfWork) {
  ...
  if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
    // 여기로 빠져나옴
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    ...
  }

  ...
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  // {children:  <App/>에 대한 React Element}

  if (next === null) {
    ...
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner$2.current = null;
}

workInProgress가 App에 대한 FiberNode로 설정된다.


​workInProgress = App

이미지

이제부터는 과정들이 비슷하므로 다른 부분들만 살펴보겠다.


​beginWork()

function beginWork(current, workInProgress, renderLanes) {
  ...
  if (current !== null) {
    ...
  } else {
    didReceiveUpdate = false;
    ...
  }

  workInProgress.lanes = NoLanes; // 0

  switch (workInProgress.tag) {
    case IndeterminateComponent:
      {
        return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
      }
		...
  }
}

App은 workInProgress = StrictMode의 createFiberFromTypeAndProps 에서 tag가 IndeterminateComponent 로 설정됐었다. 그래서 mountIndeterminateComponent 로 진입하게된다.


​mountIndeterminateComponent()

// Component = workInProgress.type = f() App
function mountIndeterminateComponent(_current, workInProgress, Component, renderLanes) {
	
  var props = workInProgress.pendingProps; // {}
  var context; // {}
  ...
  var value; // undefined
  var hasId; // undefined
	...
  
  {
    ...
    setIsRendering(true);
    ReactCurrentOwner$1.current = workInProgress;
    // renderWithHooks로 진입
    value = renderWithHooks(null, workInProgress, Component, props, context, renderLanes);
    hasId = checkDidRenderIdHook();
    setIsRendering(false);
  }
	...
}


​renderWithHooks()

2.0 : React Element 생성 에서 모든 계층의 요소들이 한 번에 React Element로 만들어지지 않는다고 말했었는데, renderWithHooks에서 App 함수가 실행되면서 App 컴포넌트의 자식 요소들이 React Element로 만들어진다.

// Component = workInProgress.type = f() App
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
  renderLanes = nextRenderLanes;
  currentlyRenderingFiber$1 = workInProgress;
	...
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;
  ...
  
  // f App()를 실행한다.
  var children = Component(props, secondArg);
	...
  return children;
}
이미지


​mountIndetermindateComponent()

function mountIndeterminateComponent(_current, workInProgress, Component, renderLanes) {
	...
  value = renderWithHooks(null, workInProgress, Component, props, context, renderLanes);
  // 여기로 빠져나온다.
  ...
  if (
   typeof value === 'object' && value !== null && typeof value.render === 'function' && value.$$typeof === undefined) {
		...   	  
  } else {
	  // 함수 컴포넌트로 결정된다!
    workInProgress.tag = FunctionComponent; // 0   
		...
		reconcileChildren(null, workInProgress, value, renderLanes);
  }
}

renderWithHooks로 자식 요소들을 React Element로 변환하고, 이 React Element로 어떤 컴포넌트인지 결정한다. 나머지 reconcileChildren() → reconcileChildFibers() → reconcileSingleElement() … 과정은 위와 동일하다.


​workInProgress = div

이미지


​beginWork()

function beginWork(current, workInProgress, renderLanes) {
  ...
  if (current !== null) {
    ...
  } else {
    didReceiveUpdate = false;
    ...
  }

  workInProgress.lanes = NoLanes; // 0

  switch (workInProgress.tag) {
    case HostComponent:
      {
        return updateHostComponent(current, workInProgress, renderLanes);
      }
		...
  }
}

div 와 같이 실제 DOM 요소에 대한 FiberNode는 tag가 HostComponent이다.


​updateHostComponent

function updateHostComponent(current, workInProgress, renderLanes) {
	...
  var type = workInProgress.type; // div
  
  // { children: Array(2) }
  // { 0: $$typeof: Symbol(react.element), props: {children: 'Hello'}, type: "p", ... }
  // { 1: $$typeof: Symbol(react.element), props: {}, type: ƒ Component(), ... }
  var nextProps = workInProgress.pendingProps;     
  var prevProps = current !== null ? current.memoizedProps : null; // null
  var nextChildren = nextProps.children; // Array(2)
  ...
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}


​reconcileChildren()

// current = null, workInProgress = App에 대한 FiberNode
// nextChildren: renderWithHooks에서 렌더링한 App의 React Element
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  if (current === null) {
    ...
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
   ...
  }
}


​reconcileChildFibers()

function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
	...
  if (typeof newChild === 'object' && newChild !== null) {
    ...
    if (isArray(newChild)) {
      return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes);
    }
    ...
  }
	...
}
function App() {
  return (
    <div>
      <p>App</p>
      <Component />
    </div>
  );
}

다른 FiberNode와는 다르게 div는 자식이 배열이므로 reconcileChildrenArray()로 진입한다.


​reconcileChildrenArray()

  • newChildren을 순회하면서 Child에 대한 FiberNode를 모두 생성한다.

  • newIdx = 0이면 previousNewFiber가 null이므로 resultingFirstChild = _newFiber;

  • newIdx = 1이면 previousNewFiber가 null이 아니므로 previousNewFiber = _newFiber 를 한다.

  • 이런 식으로 같은 계층인 요소들은 sibling으로 연결된다.

  • 다 순회하면 첫 번째로 생성한 Child를 return 한다.

function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, lanes) {
	...
  var resultingFirstChild = null;
  var previousNewFiber = null;
  var oldFiber = currentFirstChild;
  var lastPlacedIndex = 0;
  var newIdx = 0;
  var nextOldFiber = null;
	...
  if (oldFiber === null) {
    // If we don't have any more existing children we can choose a fast path
    // since the rest will all be insertions.
    for (; newIdx < newChildren.length; newIdx++) {
      var _newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
	    // 여기로 빠져나옴
      if (_newFiber === null) {
        continue;
      }

      lastPlacedIndex = placeChild(_newFiber, lastPlacedIndex, newIdx);

      if (previousNewFiber === null) {
        // TODO: Move out of the loop. This only happens for the first run.
        resultingFirstChild = _newFiber;
      } else {
        previousNewFiber.sibling = _newFiber;
      }

      previousNewFiber = _newFiber;
    }

    if (getIsHydrating()) {
      var _numberOfForks = newIdx;
      pushTreeFork(returnFiber, _numberOfForks);
    }

    return resultingFirstChild;
  } // Add all children to a key map for quick lookups.

	...
}

newIdx = 0;

이미지


newIdx = 1;

이미지



​workInProgress = p

이미지

​beginWork()

function beginWork(current, workInProgress, renderLanes) {
  ...
  if (current !== null) {
    ...
  } else {
    didReceiveUpdate = false;
    ...
  }

  workInProgress.lanes = NoLanes; // 0

  switch (workInProgress.tag) {
    case HostComponent:
      {
        return updateHostComponent(current, workInProgress, renderLanes);
      }
		...
  }
}


​updateHostComponent()

function updateHostComponent(current, workInProgress, renderLanes) {
	...
  var type = workInProgress.type; // p
  
  // { children: "Hello" }
  var nextProps = workInProgress.pendingProps;     
  var prevProps = current !== null ? current.memoizedProps : null; // null
  var nextChildren = nextProps.children; // Hello
  var isDirectTextChild = shouldSetTextContent(type, nextProps); // true

	if (isDirectTextChild) {
    nextChildren = null;
  } else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
    ...
  }
  ...
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}


​reconcileChildren()

이미지

p는 자식 노드가 <p>, <div>와 같은 노드가 아닌 텍스트 노드이다. <p>Hello</p>

그래서 더 깊이 진입하지 않고, 그냥 빠져나온다

// current = null, workInProgress = p에 대한 FiberNode
// nextChildren: null
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
  if (current === null) {
    ...
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
  } else {
   ...
  }
}


​performUnitOfWork()

beginWork 에서 자식 FiberNode가 생성되지 않으므로 next가 null이 되고, completeUnitOfWork을 호출한다.

function performUnitOfWork(unitOfWork) {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  var current = unitOfWork.alternate;
  setCurrentFiber(unitOfWork);
  var next;

  if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
  }

  resetCurrentFiber();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;

  if (next === null) {
	  // 여기로 진입    
    completeUnitOfWork(unitOfWork);
  } else {
    ...
  }

  ReactCurrentOwner$2.current = null;
}


​completeUnitOfWork()

function completeUnitOfWork(unitOfWork) {
  var completedWork = unitOfWork;

  do {
    var current = completedWork.alternate; // null
    var returnFiber = completedWork.return; // div FiberNode

		//   1048576:  100000000000000000000 (2진수)
		//   32768:         1000000000000000 (2진수)
		//   결과 -> 0
    if ((completedWork.flags & Incomplete) === NoFlags) {
      setCurrentFiber(completedWork);
      var next = void 0;

      if ( (completedWork.mode & ProfileMode) === NoMode) {
        ...
      } else {
        startProfilerTimer(completedWork);
        // 여기로 진입
        next = completeWork(current, completedWork, subtreeRenderLanes); // Update render duration assuming we didn't error.
        stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
      }
  ...
}


​completeWork()

function completeWork(current, workInProgress, renderLanes) {
  var newProps = workInProgress.pendingProps; // "Hello"

  popTreeContext(workInProgress);

  switch (workInProgress.tag) {
	  ...
    case HostComponent:
      {
        ...
        var rootContainerInstance = getRootHostContainer(); // div#root
        var type = workInProgress.type; // p

        if (current !== null && workInProgress.stateNode != null) {
          ...
        } else {
          if (!newProps) {
            ...
          }
					...
          var _wasHydrated = popHydrationState(workInProgress); // false

          if (_wasHydrated) {
            ...
          } else {
	          // 여기로 진입
            var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
	          ...
          }
					...
        }
				...
      }
  }
}


​createInstance()

function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
  var parentNamespace; // undefined
  ...
  var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
	...
  return domElement;
}


​createElement()

이미지
function createElement(type, props, rootContainerElement, parentNamespace) {
  var isCustomComponentTag; 

  var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerElement);
  var domElement;
  var namespaceURI = parentNamespace;
	...
  if (namespaceURI === HTML_NAMESPACE) {
    ...
    if (type === 'script') {
      ...
    } else if (typeof props.is === 'string') {
      ...
    } else {  
      domElement = ownerDocument.createElement(type);
			...
    }
  } else {
    ...
  }
  ...
  return domElement;
}
function getOwnerDocumentFromRootContainer(rootContainerElement) {
	// 1 === 9 -> false
  return rootContainerElement.nodeType === DOCUMENT_NODE
    ? rootContainerElement
    : rootContainerElement.ownerDocument;
}


​completeWork()

function completeWork(current, workInProgress, renderLanes) {
  var newProps = workInProgress.pendingProps; // "Hello"

  popTreeContext(workInProgress);

  switch (workInProgress.tag) {
	  ...
    case HostComponent:
      {
        ...
        var rootContainerInstance = getRootHostContainer(); // div#root
        var type = workInProgress.type; // p

        if (current !== null && workInProgress.stateNode != null) {
          ...
        } else {
          if (!newProps) {
            ...
          }
					...
          var _wasHydrated = popHydrationState(workInProgress); // false

          if (_wasHydrated) {
            ...
          } else {
            var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
						// 여기로 빠져나옴
            appendAllChildren(instance, workInProgress, false, false);
            
            // FiberNode의 stateNode에는 document.createElement로 만든 실제 요소가 할당된다.
            workInProgress.stateNode = instance;
            ...
            
            if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance)) {
							markUpdate(workInProgress);
						}
          }
					...
        }
				...
      }
  }
}
// p는 자식이 없다.
appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
  var node = workInProgress.child; // null

	// node === null
  while (node !== null) {
	  ...
  }
};


​finalizeInitialChildren()

function finalizeInitialChildren(domElement, type, props, rootContainerInstance, hostContext) {
  setInitialProperties(domElement, type, props, rootContainerInstance);

  switch (type) {
    ...
    default:
      return false;
  }
}


​setInitialProperties()

function setInitialProperties(domElement, tag, rawProps, rootContainerElement) {
  ...
  var props; // undefined

  switch (tag) { // tag = "p"
	   ...
    default:
      props = rawProps;
  }
  ...
  setInitialDOMProperties(tag, domElement, rootContainerElement, props, isCustomComponentTag);
  ...
}


​setInitialDOMProperties()

function setInitialDOMProperties(tag, domElement, rootContainerElement, nextProps, isCustomComponentTag) {
  for (var propKey in nextProps) { // propKey = "children"
    ...
    var nextProp = nextProps[propKey]; // "Hello"

    if (propKey === STYLE) {
			...
    } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
      ...
    } else if (propKey === CHILDREN) {
      if (typeof nextProp === 'string') {
        var canSetTextContent = tag !== 'textarea' || nextProp !== ''; // true

        if (canSetTextContent) {
          setTextContent(domElement, nextProp);
        }
      } else if (typeof nextProp === 'number') {
        setTextContent(domElement, '' + nextProp);
      }
    } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING || propKey === SUPPRESS_HYDRATION_WARNING) ; else if (propKey === AUTOFOCUS) ; else if (registrationNameDependencies.hasOwnProperty(propKey)) {
      if (nextProp != null) {
        if ( typeof nextProp !== 'function') {
          warnForInvalidEventListener(propKey, nextProp);
        }

        if (propKey === 'onScroll') {
          listenToNonDelegatedEvent('scroll', domElement);
        }
      }
    } else if (nextProp != null) {
      setValueForProperty(domElement, propKey, nextProp, isCustomComponentTag);
    }
  }
}


​setTextContent()

DOM API를 이용하여 실제 DOM에 텍스트를 추가한다.

이미지
var setTextContent = function (node, text) {
  if (text) {
    var firstChild = node.firstChild; // null
		...
	}
  node.textContent = text; // 실제 DOM 요소에 text(Hello) 설정
};


​completeUnitOfWork()

이미지

p에 대한 FiberNode는 형제 노드인 Component FiberNode가 있으므로 workInProgress로 이 Component FiberNode를 설정한다.

function completeUnitOfWork(unitOfWork) {
  ...
  var siblingFiber = completedWork.sibling; // f Component() FiberNode

  if (siblingFiber !== null) {
    workInProgress = siblingFiber;
    return;
  }
	...
	} while (completedWork !== null); // We've reached the root.
		...
}


​마무리

이런식으로 DFS로 순회하면서 beginWork로 FiberNode를 생성하고, completeWork 으로 HostComponent FiberNode에 대하여 실제 DOM 요소를 생성하면서 렌더 단계가 진행된다.

(tag가 HostComponent가 아닌 HostRoot, FunctionComponent는 completeWork()이 끝까지 실행 안 되고 중간에 return 된다.)

모든 completeWork()가 끝나게 되면, workInProgress는 트리의 루트인 HostRoot로 이동된다.

이미지

렌더 단계가 마무리되면, 커밋 단계로 진입한다.



​커밋 단계

이미지

​completeUnitOfWork()

if (workInProgressRootExitStatus === RootInProgress) {
  workInProgressRootExitStatus = RootCompleted; // 5
}


​renderRootSync()

workInProgressRoot = null;
workInProgressRootRenderLanes = NoLanes;
return workInProgressRootExitStatus; // 5

​performConcurrentWorkOnRoot()

function performConcurrentWorkOnRoot(root, didTimeout) {
	...
  var shouldTimeSlice = !includesBlockingLane(root, lanes) && !includesExpiredLane(root, lanes) && ( !didTimeout);
  var exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);
	// 여기로 빠져나옴 
  if (exitStatus !== RootInProgress) {    
    if (exitStatus === RootDidNotComplete) {
	     ...
    } else {
      root.finishedWork = finishedWork; // HostRoot
      root.finishedLanes = lanes; // 16
      finishConcurrentRender(root, exitStatus, lanes);
    }
  }

  ensureRootIsScheduled(root, now());

  if (root.callbackNode === originalCallbackNode) {
    // The task node scheduled for this root is the same one that's
    // currently executed. Need to return a continuation.
    return performConcurrentWorkOnRoot.bind(null, root);
  }

  return null;
}


​finishConcurrentRender()

commitRoot를 호출하여 커밋 단계가 진행된다.

function finishConcurrentRender(root, exitStatus, lanes) {
  ...
  switch (exitStatus) {
	  case RootCompleted:
	    {
	      commitRoot(root, workInProgressRootRecoverableErrors, workInProgressTransitions);
	      break;
	    }
		...	
	}
}


​commitRoot()

function commitRoot(root, recoverableErrors, transitions) {
  ...
  try {
    ...
    commitRootImpl(root, recoverableErrors, transitions, previousUpdateLanePriority);
  } finally {
		...
  }

  return null;
}


​commitRootImpl()

function commitRootImpl(root, recoverableErrors, transitions, renderPriorityLevel) {
  ...
  var finishedWork = root.finishedWork;
  var lanes = root.finishedLanes;
  ...
  root.finishedWork = null;
  root.finishedLanes = NoLanes;
  root.callbackNode = null;
  root.callbackPriority = NoLane;
  ...
  if (subtreeHasEffects || rootHasEffect) {
    ...
    commitMutationEffects(root, finishedWork, lanes);
    ...    
  } else {
    ...
  }
	...
}


​commitMutationEffects()

function commitMutationEffects(root, finishedWork, committedLanes) {
  ...
  commitMutationEffectsOnFiber(finishedWork, root);
	...
}


​commitMutationEffectsOnFiber()

이미지
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
  var current = finishedWork.alternate; // HostRoot
  var flags = finishedWork.flags;  // 1024

  switch (finishedWork.tag) {
	 ...
    case HostRoot:
      {
        recursivelyTraverseMutationEffects(root, finishedWork);
        commitReconciliationEffects(finishedWork);

        if (flags & Update) {
          {
            if (current !== null) {
              var prevRootState = current.memoizedState;

              if (prevRootState.isDehydrated) {
                try {
                  commitHydratedContainer(root.containerInfo);
                } catch (error) {
                  captureCommitPhaseError(finishedWork, finishedWork.return, error);
                }
              }
            }
          }
        }

        return;
      }
		...
  }
}


​recursivelyTraverseMutationEffects()

function recursivelyTraverseMutationEffects(root, parentFiber, lanes) {
  ...
  if (parentFiber.subtreeFlags & MutationMask) {
    var child = parentFiber.child; // StrictMode FiberNode

    while (child !== null) {
      ...
      commitMutationEffectsOnFiber(child, root);
      ...
    }
  }

  setCurrentFiber(prevDebugFiber);
}


​commitMutationEffectsOnFiber()

이미지
function commitMutationEffectsOnFiber(finishedWork, root, lanes) {
  var current = finishedWork.alternate; // null
  var flags = finishedWork.flags; // 2
  
  switch (finishedWork.tag) {
	  ...
    default:
      {
	      // 여기로 진입
        recursivelyTraverseMutationEffects(root, finishedWork);
        commitReconciliationEffects(finishedWork);
        return;
      }
  }
}


​recursivelyTraverseMutationEffects()

// 그냥 빠져나옴
function recursivelyTraverseMutationEffects(root, parentFiber, lanes) {
 ...
}


​commitReconciliationEffects()

// finishWork = StrictMode
function commitReconciliationEffects(finishedWork) {
  var flags = finishedWork.flags; // 2

	// StrictMode의 flags는 Placement이다.
  if (flags & Placement) {
    try {
      commitPlacement(finishedWork);
    } catch (error) {
			...
    }
  }
  ...
}


​commitPlacement()

function commitPlacement(finishedWork) {
  var parentFiber = getHostParentFiber(finishedWork); // HostRoot

  switch (parentFiber.tag) {
  	...
    case HostRoot:
    case HostPortal:
      {
        var _parent = parentFiber.stateNode.containerInfo; // div#root

        var _before = getHostSibling(finishedWork); // null

        insertOrAppendPlacementNodeIntoContainer(finishedWork, _before, _parent);
        break;
      }
    ...
  }
}


​insertOrAppendPlacementNodeIntoContainer()

function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {
  var tag = node.tag; // 8
  var isHost = tag === HostComponent || tag === HostText; // false

  if (isHost) {
    var stateNode = node.stateNode;

    if (before) {
      insertInContainerBefore(parent, stateNode, before);
    } else {
      appendChildToContainer(parent, stateNode);
    }
  } else if (tag === HostPortal) ; else {
    var child = node.child; // App FiberNode

    if (child !== null) {
      insertOrAppendPlacementNodeIntoContainer(child, before, parent);
      var sibling = child.sibling;

      while (sibling !== null) {
        insertOrAppendPlacementNodeIntoContainer(sibling, before, parent);
        sibling = sibling.sibling;
      }
    }
  }
}

tag가 HostComponent일 때까지 재귀 호출한다.


이미지이미지이미지이미지
function insertOrAppendPlacementNodeIntoContainer(node, before, parent) {
  var tag = node.tag; // 8
  var isHost = tag === HostComponent || tag === HostText; // false

  if (isHost) {
    var stateNode = node.stateNode;

    if (before) {
      ...
    } else {
      appendChildToContainer(parent, stateNode);
    }
  } else if (tag === HostPortal) ; else {
    ...
  }
}

child를 따라 내려가다가 Node의 Tag가 HostComponent면 appendChildToContainer 를 호출하여 containerInfo의 자식으로 Node를 추가한다.

function appendChildToContainer(container, child) {
  var parentNode;

  if (container.nodeType === COMMENT_NODE) {
    ...
  } else {
    parentNode = container; // div#root
    parentNode.appendChild(child); // div#root.appendChild(div)
  } 
  ...
}

Node의 flags 를 Placement에서 0으로 초기화한다. finishedWork.flags &= ~Placement;

이제 콜스택에 남아있는 함수를 다 빠져나오면서 커밋 단계가 마무리된다.


이미지