React 첫 렌더링이 되는 과정(1)

HANITZ·2023년 12월 27일
0

React

목록 보기
3/8
post-thumbnail

리액트가 첫 렌더링을 하는 과정을 코드를 보면서 단계들을 분석해보려고 한다.

모든 코드를 살펴보진 않고 렌더링의 핵심이 되는 로직들만 보도록 하겠다.


처음 렌더링의 시작은 index.tsx에서 시작한다.

// index.tsx
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(<App />)

index.html에는 root라는 id를 가진 div태그가 설정되어있고 이 div를 가져와서 createRoot()함수에 넣어준 값이 root이다.

먼저 createRoot를 따라가 보겠다.

// react-dom/src/client/ReactDOMRoot.js
export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {

  const root = createContainer(
    container,
    ConcurrentRoot,
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
  );
  markContainerAsRoot(root.current, container);

  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container;
  listenToAllSupportedEvents(rootContainerElement);

  return new ReactDOMRoot(root);
}
  • createContainer함수는 FiberRoot를 반환해오는 함수이다. 내부적으로 Virtual DOM의 root역할이다.
  • markContainerAsRoot는 현재 컨테이너가 root인지 확인할 수 있게 표시를 해놓는 작업이다. 이후 자식 노드들부터 업데이트가 발생해서 부모로 올라오는데 이때 루트 노드이면 작업을 멈춰야하기 때문에 현재의 위치를 확인해주는 역할이다.

  • listenToAllSupportedEvent함수에서 모든 이벤트 리스너를 root element에 붙여준다.

  • ReactDOMRoot는 this._internalRoot에 root를 주입한 객체가 반환됨

createContainer

// react-reconciler/src/ReactFiberReconciler.new.js
export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
  identifierPrefix: string,
  onRecoverableError: (error: mixed) => void,
  transitionCallbacks: null | TransitionTracingCallbacks,
): OpaqueRoot {
  const hydrate = false;
  const initialChildren = null;
  return createFiberRoot(
    containerInfo,
    tag,
    hydrate,
    initialChildren,
    hydrationCallbacks,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
  );
}

hydrate와 initialChildren을 초기화하고 createFiberRoot로 넘긴다

// react-reconciler/src/ReactFiberRoot.new.js
export function createFiberRoot(
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
  initialChildren: ReactNodeList,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,

  identifierPrefix: string,
  onRecoverableError: null | ((error: mixed) => void),
  transitionCallbacks: null | TransitionTracingCallbacks,
): FiberRoot {
  const root: FiberRoot = (new FiberRootNode(
    containerInfo,
    tag,
    hydrate,
    identifierPrefix,
    onRecoverableError,
  ): any);
  
  //...

  const uninitializedFiber = createHostRootFiber(
    tag,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
  );
  root.current = uninitializedFiber;
  uninitializedFiber.stateNode = root;

  if (enableCache) {
    const initialCache = createCache();
    retainCache(initialCache);

    root.pooledCache = initialCache;
    retainCache(initialCache);
    const initialState: RootState = {
      element: initialChildren,
      isDehydrated: hydrate,
      cache: initialCache,
      transitions: null,
      pendingSuspenseBoundaries: null,
    };
    uninitializedFiber.memoizedState = initialState;
  } else {
    const initialState: RootState = {
      element: initialChildren,
      isDehydrated: hydrate,
      cache: (null: any), // not enabled yet
      transitions: null,
      pendingSuspenseBoundaries: null,
    };
    uninitializedFiber.memoizedState = initialState;
  }

  initializeUpdateQueue(uninitializedFiber);

  return root;
}
  • root 는 생성자 함수 FiberRootNode로 아래와 같은 객체를 생성한다
// react-reconciler/src/ReactFiberRoot.new.js
function FiberRootNode(
  containerInfo,
  tag,
  hydrate,
  identifierPrefix,
  onRecoverableError,
) {
  this.tag = tag;
  this.containerInfo = containerInfo;
  this.pendingChildren = null;
  this.current = null;
  this.pingCache = null;
  this.finishedWork = null;
  this.timeoutHandle = noTimeout;
  this.context = null;
  this.pendingContext = null;
  this.callbackNode = null;
  this.callbackPriority = NoLane;
  this.eventTimes = createLaneMap(NoLanes);
  this.expirationTimes = createLaneMap(NoTimestamp);

  this.pendingLanes = NoLanes;
  this.suspendedLanes = NoLanes;
  this.pingedLanes = NoLanes;
  this.expiredLanes = NoLanes;
  this.mutableReadLanes = NoLanes;
  this.finishedLanes = NoLanes;

  this.entangledLanes = NoLanes;
  this.entanglements = createLaneMap(NoLanes);

  this.hiddenUpdates = createLaneMap(null);

  this.identifierPrefix = identifierPrefix;
  this.onRecoverableError = onRecoverableError;
  // ....
}
  • uninitializedFiber는 createHostRootFiber 함수에서 HostRoot tag를 가진 Fiber 객체를 반환받는다.

// react-reconciler/src/ReactFiber.new.js

export function createHostRootFiber(
  tag: RootTag,
  isStrictMode: boolean,
  concurrentUpdatesByDefaultOverride: null | boolean,
): Fiber {
  let mode;
  if (tag === ConcurrentRoot) {
    mode = ConcurrentMode;
    if (isStrictMode === true) {
      mode |= StrictLegacyMode;

      if (enableStrictEffects) {
        mode |= StrictEffectsMode;
      }
    } else if (enableStrictEffects && createRootStrictEffectsByDefault) {
      mode |= StrictLegacyMode | StrictEffectsMode;
    }
    if (
      !enableSyncDefaultUpdates ||
      // Only for internal experiments.
      (allowConcurrentByDefault && concurrentUpdatesByDefaultOverride)
    ) {
      mode |= ConcurrentUpdatesByDefaultMode;
    }
  } else {
    mode = NoMode;
  }

  if (enableProfilerTimer && isDevToolsPresent) {
    mode |= ProfileMode;
  }

  return createFiber(HostRoot, null, null, mode);
}

const createFiber = function(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
): Fiber {
  // $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
  return new FiberNode(tag, pendingProps, key, mode);
};

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  // Instance
  this.tag = tag;
  this.key = key;
  this.elementType = null;
  this.type = null;
  this.stateNode = null;

  // Fiber
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  this.pendingProps = pendingProps;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;
  this.dependencies = null;

  this.mode = mode;

  // Effects
  this.flags = NoFlags;
  this.subtreeFlags = NoFlags;
  this.deletions = null;

  this.lanes = NoLanes;
  this.childLanes = NoLanes;

  this.alternate = null;

}

createHostRootFiber에서 mode를 정해주고 createFiber로 넘겨준다.
createFiber는 Fiber객체를 반환해준다.

  • initialState는 HostRoot의 state로 캐싱여부확인과 함께 초기화하여 HostRoot의 memoizedState에 넣어준다.
// react-reconciler/src/ReactFiberClassUpdateQueue.new.js
export function initializeUpdateQueue<State>(fiber: Fiber): void {
  const queue: UpdateQueue<State> = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
      pending: null,
      lanes: NoLanes,
    },
    effects: null,
  };
  fiber.updateQueue = queue;
}
  • initializeUpdateQueue함수로 HostRoot에 updateQueue를 할당해주는 작업을 한다.
// react-dom/src/client/ReactDOMRoot.js
export function createRoot(
  container: Element | Document | DocumentFragment,
  options?: CreateRootOptions,
): RootType {

  const root = createContainer(
    container,
    ConcurrentRoot,
    null,
    isStrictMode,
    concurrentUpdatesByDefaultOverride,
    identifierPrefix,
    onRecoverableError,
    transitionCallbacks,
  );
  markContainerAsRoot(root.current, container);

  const rootContainerElement: Document | Element | DocumentFragment =
    container.nodeType === COMMENT_NODE
      ? (container.parentNode: any)
      : container;
  listenToAllSupportedEvents(rootContainerElement);

  return new ReactDOMRoot(root);
}

결국 createRoot의 root는 FiberRoot라는 노드이다.

이 FiberRoot의 current에 HostRoot인 fiber노드를 갖고있다.

이 HostRoot가 실질적인 root 역할을 한다.

HostRoot는 기본 state와 updateQueue를 가지고 있다.

markContainerAsRoot

// react-dom/src/client/ReactDOMComponentTree.js

const randomKey = Math.random()
  .toString(36)
  .slice(2);
const internalContainerInstanceKey = '__reactContainer$' + randomKey;

export function markContainerAsRoot(hostRoot: Fiber, node: Container): void {
  node[internalContainerInstanceKey] = hostRoot;
}

root 엘리먼트에 hostRoot를 연결시키는 작업이다.

listenToAllSupportedEvents는 root 엘리먼트에 모든 이벤트리스너를 붙여주는 역할을 한다.

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

그 후 ReactDOMRoot 객체를 생성하여 _internalRoot에 root를 주입 후 반환한다.

이렇게 ReactDOM 객체가 만들어진다.

render

다음으로 render 함수를 보겠다.

// react-dom/src/client/ReactDOMRoot.js

ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function(
  children: ReactNodeList,
): void {
  const root = this._internalRoot;
  
  //...
  
  updateContainer(children, root, null, null);
};

_internalRoot를 가져와 children과 함께 updateContainer함수를 실행시킨다.

// react-reconciler/src/ReactFiberReconciler.new.js

export function updateContainer(
  element: ReactNodeList, // children
  container: OpaqueRoot,  // root
  parentComponent: ?React$Component<any, any>, // null
  callback: ?Function, // null
): Lane {

  const current = container.current; // HostRoot
  const eventTime = requestEventTime(); // now
  const lane = requestUpdateLane(current); // Concurrent

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  const update = createUpdate(eventTime, lane);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  update.payload = {element};

  callback = callback === undefined ? null : callback;
  if (callback !== null) {

    update.callback = callback;
  }

  const root = enqueueUpdate(current, update, lane);
  if (root !== null) {
    scheduleUpdateOnFiber(root, current, lane, eventTime);
    entangleTransitions(root, current, lane);
  }

  return lane;
}


function getContextForSubtree(
  parentComponent: ?React$Component<any, any>,
): Object {
  if (!parentComponent) {
    return emptyContextObject;
  }

  const fiber = getInstance(parentComponent);
  const parentContext = findCurrentUnmaskedContext(fiber);

  if (fiber.tag === ClassComponent) {
    const Component = fiber.type;
    if (isLegacyContextProvider(Component)) {
      return processChildContext(fiber, Component, parentContext);
    }
  }

  return parentContext;
}
  • context는 getContextForSubtree에서 context 정보를 반환하는데 현재 parentComponent가 null이기 때문에 {}가 반환된다.

createUpdate, enqueueUpdate

// react-reconciler/src/ReactFiberClassUpdateQueue.new.js

export function createUpdate(eventTime: number, lane: Lane): Update<*> {
  const update: Update<*> = {
    eventTime,
    lane,

    tag: UpdateState,
    payload: null,
    callback: null,

    next: null,
  };
  return update;
}

export function enqueueUpdate<State>(
  fiber: Fiber,  // hostRoot
  update: Update<State>, // update.payload = children
  lane: Lane,
): FiberRoot | null {
  const updateQueue = fiber.updateQueue;
  if (updateQueue === null) {
    // Only occurs if the fiber has been unmounted.
    return null;
  }

  const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;


  if (isUnsafeClassRenderPhaseUpdate(fiber)) {

    const pending = sharedQueue.pending;
    if (pending === null) {

      update.next = update;
    } else {
      update.next = pending.next;
      pending.next = update;
    }
    sharedQueue.pending = update;

    return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane);
  } else {
    return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane);
  }
}
  • update는 createUpdate에서 eventTime와 lane이 할당된 연결리스트 객체를 받아온다.
  • update.payload에 children(<App />)을 넣어준다.
  • callback은 null이기 때문에 따로 할당되지 않는다.

  • enqueueUpdate에서 기존에 fiber에 넣어놨던 updateQueue객체를 updateQueue로 불러온다.
  const queue: UpdateQueue<State> = {
    baseState: fiber.memoizedState,
    firstBaseUpdate: null,
    lastBaseUpdate: null,
    shared: {
      pending: null,
      lanes: NoLanes,
    },
    effects: null,
  };
  • 그 후 객체 안의 shared를 sharedQueue로 불러온다.

  • isUnsafeClassRenderPhaseUpdate함수는 render phase인지 확인해주는 함수이다. 지금은 첫 렌더링이기 때문에 false를 반환한다.

// react-reconciler/src/ReactFiberConcurrentUpdates.new.js

const concurrentQueues: Array<any> = [];
let concurrentQueuesIndex = 0;

let concurrentlyUpdatedLanes: Lanes = NoLanes;

export function enqueueConcurrentClassUpdate<State>(
  fiber: Fiber, // hostRoot
  queue: ClassQueue<State>, // sharedQueue
  update: ClassUpdate<State>, // update
  lane: Lane,
): FiberRoot | null {
  const concurrentQueue: ConcurrentQueue = (queue: any);
  const concurrentUpdate: ConcurrentUpdate = (update: any);
  enqueueUpdate(fiber, concurrentQueue, concurrentUpdate, lane);
  return getRootForUpdatedFiber(fiber);
}

function enqueueUpdate(
  fiber: Fiber, // hostRoot
  queue: ConcurrentQueue | null, // sharedQueue
  update: ConcurrentUpdate | null, // update
  lane: Lane,
) {

  concurrentQueues[concurrentQueuesIndex++] = fiber;
  concurrentQueues[concurrentQueuesIndex++] = queue;
  concurrentQueues[concurrentQueuesIndex++] = update;
  concurrentQueues[concurrentQueuesIndex++] = lane;

  concurrentlyUpdatedLanes = mergeLanes(concurrentlyUpdatedLanes, lane);

  fiber.lanes = mergeLanes(fiber.lanes, lane);
  const alternate = fiber.alternate;
  if (alternate !== null) {
    alternate.lanes = mergeLanes(alternate.lanes, lane);
  }
}

function getRootForUpdatedFiber(sourceFiber: Fiber): FiberRoot | null {

  detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);
  let node = sourceFiber;
  let parent = node.return;
  while (parent !== null) {
    detectUpdateOnUnmountedFiber(sourceFiber, node);
    node = parent;
    parent = node.return;
  }
  return node.tag === HostRoot ? (node.stateNode: FiberRoot) : null;
}
  • enqueueUpdate에서 현재 fiber, queue, update, lane을 concurrentQueues에 할당해주고 lane을 merge시켜 정리해준다.

  • getRootForUpdatedFiber는 root노드의 tag가 HostRoot인지 확인해주는 함수이다. HostRoot이면 hostRoot그대로를 반환하고 아니면 null을 반환한다.

결국 상단의 updateContainer에서 root는 hostRoot이거나 null이다.

export function updateContainer(
  element: ReactNodeList, // children
  container: OpaqueRoot,  // root
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {

  const current = container.current; // HostRoot
  const eventTime = requestEventTime(); // now
  const lane = requestUpdateLane(current); // Concurrent

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  const update = createUpdate(eventTime, lane);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  update.payload = {element};

  callback = callback === undefined ? null : callback;
  if (callback !== null) {

    update.callback = callback;
  }

  const root = enqueueUpdate(current, update, lane); // hostRoot or null
  if (root !== null) {
    scheduleUpdateOnFiber(root, current, lane, eventTime);
    entangleTransitions(root, current, lane);
  }

  return lane;
}

scheduleUpdateOnFiber

export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
) {
  checkForNestedUpdates();


  markRootUpdated(root, lane, eventTime);

  if (
    (executionContext & RenderContext) !== NoLanes &&
    root === workInProgressRoot
  ) {
    warnAboutRenderPhaseUpdatesInDEV(fiber);

    workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
      workInProgressRootRenderPhaseUpdatedLanes,
      lane,
    );
  } else {

	.
    .
    ,

    if (root === workInProgressRoot) {
      if (
        deferRenderPhaseUpdateToNextBatch ||
        (executionContext & RenderContext) === NoContext
      ) {
        workInProgressRootInterleavedUpdatedLanes = mergeLanes(
          workInProgressRootInterleavedUpdatedLanes,
          lane,
        );
      }
      if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
        markRootSuspended(root, workInProgressRootRenderLanes);
      }
    }

    ensureRootIsScheduled(root, eventTime);

  }
}

먼저 checkForNestedUpdates함수로 update횟수를 검사한다. 이는 리렌더링이 무한으로 발생하는 현상들을 사전에 검사하기 위한 함수이다. 업데이트 카운트가 50회를 초과한다면 에러를 발생시킨다.

// react-reconciler/src/ReactFiberWorkLoop.new.js

const NESTED_UPDATE_LIMIT = 50;
let nestedUpdateCount: number = 0;
function checkForNestedUpdates() {
  if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
    nestedUpdateCount = 0;
    rootWithNestedUpdates = null;

    throw new Error(
      'Maximum update depth exceeded. This can happen when a component ' +
        'repeatedly calls setState inside componentWillUpdate or ' +
        'componentDidUpdate. React limits the number of nested updates to ' +
        'prevent infinite loops.',
    );
  }
}

markRootUpdated 함수는 root의 eventTimes에 lane을 기록해서 업데이트가 언제 요청되었는지 알 수 있게 해준다.

// react-reconciler/src/ReactFiberLane.new.js

export function markRootUpdated(
  root: FiberRoot,
  updateLane: Lane,
  eventTime: number,
) {
  root.pendingLanes |= updateLane;

  if (updateLane !== IdleLane) {
    root.suspendedLanes = NoLanes;
    root.pingedLanes = NoLanes;
  }

  const eventTimes = root.eventTimes;
  const index = laneToIndex(updateLane);
  eventTimes[index] = eventTime;
}

다시 scheduleUpdateOnFiber 함수로 돌아가서..

(executionContext & RenderContext) !== NoLanes &&
root === workInProgressRoot 의 조건은 현재 업데이트가 render phase 중에 발생했냐는 조건이다. 지금은 초기 렌더링이기 때문에 workInProgressRoot는 null이므로 조건에 맞지않다.

바로 ensureRootIsScheduled 함수로 가면

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  const existingCallbackNode = root.callbackNode; // null

 
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  ); // DefaultLanes

  if (nextLanes === NoLanes) {
    if (existingCallbackNode !== null) {
      cancelCallback(existingCallbackNode);
    }
    root.callbackNode = null;
    root.callbackPriority = NoLane;
    return;
  }

  const newCallbackPriority = getHighestPriorityLane(nextLanes); // DefaultLanes

  const existingCallbackPriority = root.callbackPriority; // NoLanes

  if (existingCallbackNode != null) {
    cancelCallback(existingCallbackNode);
  }


  let newCallbackNode;
  if (newCallbackPriority === SyncLane) {
    if (root.tag === LegacyRoot) {
      scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
    } else {
      scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    }
    if (supportsMicrotasks) {
      if (__DEV__ && ReactCurrentActQueue.current !== null) {

        ReactCurrentActQueue.current.push(flushSyncCallbacks);
      } else {
        scheduleMicrotask(() => {
          if (
            (executionContext & (RenderContext | CommitContext)) ===
            NoContext
          ) {

            flushSyncCallbacks();
          }
        });
      }
    } else {
      scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
    }
    newCallbackNode = null;
  } else {
    let schedulerPriorityLevel;
    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }

  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}

newCallbackPriority은 DefaultLanes 이기때문에 SyncLane과 다르다.

if (newCallbackPriority === SyncLane) {
 	.
    .
    .
  } else {
    let schedulerPriorityLevel;
    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }

  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;

schedulePriorityLevel은 nextLanes가 DefaultLanes이기때문에 NormalPriority가 되고
scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root))를 실행한다.

// react-reconciler/src/ReactFiberWorkLoop.new.js

function scheduleCallback(priorityLevel, callback) {
  if (__DEV__) {

    const actQueue = ReactCurrentActQueue.current;
    if (actQueue !== null) {
      actQueue.push(callback);
      return fakeActCallbackNode;
    } else {
      return Scheduler_scheduleCallback(priorityLevel, callback);
    }
  } else {
    // In production, always call Scheduler. This function will be stripped out.
    return Scheduler_scheduleCallback(priorityLevel, callback);
  }
}

Scheduler_scheduleCallback(priorityLevel, callback)를 바로 실행한다.

// react-reconciler/src/Scheduler.js

import * as Scheduler from 'scheduler';

export const scheduleCallback = Scheduler.unstable_scheduleCallback;

scheduleCallback는 reconciler가 아닌 scheduler에서 주입된 함수이기 때문에 scheduler에서 찾아야한다.

중간 정리를 한번 하자면...

render :

_internalRoot에 root를 주입하고 updateContainer함수 실행

updateContainer :

현재의 eventTime, lane, context 받아와 새로운 update 객체 초기화 후 sharedQueue에 정리 -> scheduleUpdateOnFiber함수 실행

scheduleUpdateOnFiber :

Lane을 workInProgress의 상태에따라 다르게 정리해주는 함수.
초기 렌더링이기때문에 바로 ensureRootIsScheduled함수 실행

ensureRootIsScheduled :

Lane에 따라 다른 scheduleSyncCallback함수를 실행해준다. 조건이 달라도 결국 scheduleCallback함수에 performWorkOnRoot.bind(null)를 넣어서 실행해주는 형태이다.

지금은
scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root))를 실행한다.

perfomrWorkOnRoot함수는 실질적으로 렌더링을 담당하는 entry point 함수이다. 때문에 reconciler에서 동작하는 entry point함수를 scheduler에 넘겨주면서 scheduler에서 담당하는 작업을 마무리하면 다시 들어올 수 있게 해주는 장치이다.


글이 너무 길어지는 것 같아서 scheduler부터는 다음 글 부터 작성하겠다.

0개의 댓글

관련 채용 정보