The article background

This article mainly talks about two parts of the content, one is to introduce the main process of render function execution, including mounting and rendering JSX nodes, to explain the internal call function; The other was to refine some of the important things that the Render phase did, including internal calculations of Fiber and task scheduling.

Get into the business

We know that JSX elements are rendered on the page, React.createElement -> React.render (FiberRootNode) -> Create the update object -> process the update queue -> Enter the commit phase -> Render complete

This is the main flow of render function execution

So, let’s look at the react. createElement function first.

React.createElement

/ / JSX code < div id = '1' > 1 < / div > / / convert the React. The createElement method (" div ", {id: "1"}, "1")Copy the code

The createElement function will take three arguments: In general, type is the DOM node name, class component, or function component. Config contains the ref, key, props and other configuration parameters. Children is the content contained in this DOM node, which may be a common element mentioned above or an array. In the current function there’s always a judgment process.

function createElement(type, config, children) { var propName; var props = {}; var key = null; var ref = null; var self = null; var source = null; if (config ! = null) { if (hasValidRef(config)) { ref = config.ref; { warnIfStringRefCannotBeAutoConverted(config); } } if (hasValidKey(config)) { key = '' + config.key; } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object for (propName in config) { if (hasOwnProperty.call(config, propName) && ! RESERVED_PROPS.hasOwnProperty(propName)) { props[propName] = config[propName]; } } } var childrenLength = arguments.length - 2; if (childrenLength === 1) { props.children = children; } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } { if (Object.freeze) { Object.freeze(childArray); } } props.children = childArray; } // Resolve default props if (type && type.defaultProps) { var defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } {if (key || ref) { var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type; if (key) { defineKeyPropWarningGetter(props, displayName); } if (ref) { defineRefPropWarningGetter(props, displayName); } } } return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props); }Copy the code

The different arguments are processed and a ReactElement Object (a plain Object) is returned.

var ReactElement = function (type, key, ref, self, source, owner, props) {
  var element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner
  };
  {
    element._store = {}; 
    Object.defineProperty(element._store, 'validated', {
      configurable: false,
      enumerable: false,
      writable: true,
      value: false
    });
    Object.defineProperty(element, '_self', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: self
    });
    Object.defineProperty(element, '_source', {
      configurable: false,
      enumerable: false,
      writable: false,
      value: source
    });
    if (Object.freeze) {
      Object.freeze(element.props);
      Object.freeze(element);
    }
  }
  return element;
};
Copy the code

$$typeof is an Object that identifies whether the Object is a certain typeof ReactElement props, which contains all properties of the ReactElement. After creating the ReactElement with the children special attribute, the next step is to call render’s entry function.

React.render

We often write this line of code in the React app entry page

ReactDOM.render(<APP />, document.getElementById('root')
Copy the code

This line tells the React app that we want to render a component in a container. So let’s take a look at what the render function actually does.

As you can see, its first argument is the ReactElement object generated earlier.

function render(element, container, callback) {
  return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}
Copy the code

Render invokes legacyRenderSubtreeIntoContainer to mount and updated

function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) { var root = container._reactRootContainer; var fiberRoot; if (! Root) {/ / by calling the legacyCreateRootFromDOMContainer method its return value assigned to the container. The _reactRootContainer root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate); fiberRoot = root._internalRoot; if (typeof callback === 'function') { var originalCallback = callback; callback = function () { var instance = getPublicRootInstance(fiberRoot); originalCallback.call(instance); }; } unbatchedUpdates(function () { updateContainer(children, fiberRoot, parentComponent, callback); }); } else { fiberRoot = root._internalRoot; if (typeof callback === 'function') { var _originalCallback = callback; callback = function () { var instance = getPublicRootInstance(fiberRoot); _originalCallback.call(instance); }; } updateContainer(children, fiberRoot, parentComponent, callback); } return getPublicRootInstance(fiberRoot); }Copy the code

When we first start the project and execute the reactdom. render method, there will be no value to get container._reactrootContainer, so we’ll just focus on handling the code without root:

When there is no root, call legacyCreateRootFromDOMContainer method return value assigned to the container. The _reactRootContainer, and create FiberRoot; Since initializations cannot be batch handled, that is, synchronized updates, call the unbatchedUpdates method directly, which I’ll discuss later.

function legacyCreateRootFromDOMContainer(container, ForceHydrate) {/ / determine whether need to merge the var shouldHydrate = forceHydrate | | shouldHydrateDueToLegacyHeuristic (container); // For client rendering, remove all elements from the container if (! shouldHydrate) { var warned = false; var rootSibling; while (rootSibling = container.lastChild) { container.removeChild(rootSibling); }} // Return createLegacyRoot(container, shouldHydrate? { hydrate: true } : undefined); }Copy the code

Here will call shouldHydrateDueToLegacyHeuristic function to determine whether need to merge, when the client rendering, need to remove all the elements in the container. The createLegacyRoot function is called to create a new Instance of ReactDOMLegacyRoot.

function createLegacyRoot(container, options) {
  return new ReactDOMLegacyRoot(container, options);
}
Copy the code

The createRootImpl function is executed internally within the new ReactDOMLegacyRoot

function ReactDOMLegacyRoot(container, options) {
  this._internalRoot = createRootImpl(container, LegacyRoot, options);
}
Copy the code

In this section we are going to look at the difference between FiberRoot and RootFiber and the connection between them. See below

function createRootImpl(container, tag, options) { // Tag is either LegacyRoot or Concurrent Root var hydrate = options ! = null && options.hydrate === true; var hydrationCallbacks = options ! = null && options.hydrationOptions || null; var mutableSources = options ! = null && options.hydrationOptions ! = null && options.hydrationOptions.mutableSources || null; var isStrictMode = options ! = null && options.unstable_strictMode === true; // Create a fiberRoot var root = createContainer(Container, tag, Hydrate, hydrationCallbacks, isStrictMode); // Append an internal attribute to the container that points to the rootFiber node markContainerAsRoot(root.current, container) corresponding to fiberRoot's current attribute; var rootContainerElement = container.nodeType === COMMENT_NODE ? container.parentNode : container; listenToAllSupportedEvents(rootContainerElement); if (mutableSources) { for (var i = 0; i < mutableSources.length; i++) { var mutableSource = mutableSources[i]; registerMutableSourceForHydration(root, mutableSource); } } return root; }Copy the code

From the above source code, we can see that the createRootImpl method creates a fiberRoot instance by calling the createContainer method and returns that instance and assigns it to the _internalRoot property, an internal member of the ReactSyncRoot constructor. The createFiberRoot method creates fiberRoot and rootFiber and references each other

Let’s move on to the createFiberRoot function

function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks, isStrictMode, ConcurrentUpdatesByDefaultOverride) {/ / by FiberRootNode constructor creates an instance fiberRoot var root = new FiberRootNode (containerInfo, tag, hydrate); // Create the root node of the fiber tree by createHostRootFiber. Each DOM node or component generates a corresponding Fiber node (the process will be explained in a future article) // Var uninitializedFiber = createHostRootFiber(Tag, isStrictMode); // After creating rootFiber, the fiberRoot instance's current property points to the newly created rootFiber root.current = uninitializedFiber; / / rootFiber stateNode property will point to fiberRoot instance at the same time, the formation of mutual reference uninitializedFiber. StateNode = root; { var initialCache = new Map(); root.pooledCache = initialCache; var initialState = { element: null, cache: initialCache }; uninitializedFiber.memoizedState = initialState; } // Initialize the update queue initializeUpdateQueue(uninitializedFiber); // Return root for the created fiberRoot instance; }Copy the code

Create a fiberRoot instance by creating a New FiberRootNode constructor and output the instantiation results. You can see that the FiberRootNode instance contains a number of properties that play a role in the task scheduling phase

function FiberRootNode(containerInfo, tag, hydrate) { 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.hydrate = hydrate; this.callbackNode = null; // Only one task is maintained on each fiberRoot instance, which is stored in the callbackNode property this.callbackPriority = NoLane; // Priority of the current task //... }}Copy the code

Then the createHostRootFiber method creates the root node of the Fiber Tree, rootFiber

Var createFiber = function (tag, pendingProps, key, mode) { Return new FiberNode(Tag, pendingProps, key, mode); };Copy the code

The FiberNode constructor is used to create a FiberNode instance, that is, a fiber node

function FiberNode(tag, pendingProps, key, mode) {
  // 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;
}
Copy the code

So far, we have successfully created a fiber node, which forms a fiber tree corresponding to the DOM tree structure and is based on a single-linked list tree structure. The newly created Fiber node serves as the root node of the whole Fiber tree, namely RootFiber node.

Finally back to legacyRenderSubtreeIntoContainer function, interface is performed at last

return getPublicRootInstance(fiberRoot)
Copy the code

This line of code

function getPublicRootInstance(container) {
  var containerFiber = container.current;
  if (!containerFiber.child) {
    return null;
  }
  switch (containerFiber.child.tag) {
    case HostComponent:
      return getPublicInstance(containerFiber.child.stateNode);
    default:
      return containerFiber.child.stateNode;
  }
}
Copy the code

To parse what this method does, first get the current fiber node, rootFiber; Return null if rootFiber has no child node; The rest of the case, then go back containerFiber. Child. StateNode instance of child nodes

At this point, the main flow of the Render function is basically down.


Next up is the second piece, which takes a closer look at what the Render phase does to complement the segmentation we didn’t cover earlier. Space is limited, here are some difficult functions to explain…

The first step is to go back to the render function entrance

function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) { var root = container._reactRootContainer; var fiberRoot; if (! root) { //... unbatchedUpdates(function () { updateContainer(children, fiberRoot, parentComponent, callback); }); } else { //... updateContainer(children, fiberRoot, parentComponent, callback); } return getPublicRootInstance(fiberRoot); }Copy the code

There is an updateContainer function that is called with or without root. So what does this one do?

Function updateContainer(Element, container, parentComponent, callback) {Fiber var current$1 = container.current;  var eventTime = requestEventTime(); var lane = requestUpdateLane(current$1); var context = getContextForSubtree(parentComponent); if (container.context === null) { container.context = context; } else { container.pendingContext = context; } // Create an update task var update = createUpdate(eventTime, lane); update.payload = { element: element }; callback = callback === undefined ? null : callback; if (callback ! == null) { update.callback = callback; } // Insert the task into Fiber's update queue enqueueUpdate(current$1, update); var root = scheduleUpdateOnFiber(current$1, lane, eventTime); if (root ! == null) { entangleTransitions(root, current$1, lane); } return lane; }Copy the code

CreateUpdate (enqueueUpdate, enqueueUpdate); createUpdate (enqueueUpdate, enqueueUpdate); Finally, scheduleUpdateOnFiber is called to schedule the task.

Fiber (scheduleUpdateOnFiber); scheduleUpdateOnFiber (scheduleUpdateOnFiber); scheduleUpdateOnFiber (scheduleUpdateOnFiber);

scheduleUpdateOnFiber

The scheduleUpdateOnFiber function is called in the updateContainer function to handle priorities and mount update nodes.

function scheduleUpdateOnFiber(fiber, lane, eventTime) { checkForNestedUpdates(); Update lanes to root.pendingLanes markRootUpdated(root, lane, eventTime); if (root === workInProgressRoot) { { workInProgressRootUpdatedLanes = mergeLanes(workInProgressRootUpdatedLanes, lane); } if (workInProgressRootExitStatus === RootSuspendedWithDelay) { markRootSuspended$1(root, workInProgressRootRenderLanes); } } if (lane === SyncLane) { if ( // Check if we're inside unbatchedUpdates (executionContext & LegacyUnbatchedContext) ! == NoContext && // Check if we're not already rendering (executionContext & (RenderContext | CommitContext)) === NoContext) { performSyncWorkOnRoot(root); } else { ensureRootIsScheduled(root, eventTime); if (executionContext === NoContext && (fiber.mode & ConcurrentMode) === NoMode) { resetRenderTimer(); flushSyncCallbacksOnlyInLegacyMode(); } } } else { ensureRootIsScheduled(root, eventTime); } return root; }Copy the code

“EnsureRootIsScheduled” is one of the ensureRootIsScheduled functions that is used to schedule tasks and to implement, reuse, or cancel existing tasks with respect to new ones in the case of multiple tasks. In the case of a single task, synchronous or asynchronous scheduling decisions can be made on the task. In short, this function is called every time the task is updated and exits.

function ensureRootIsScheduled(root, currentTime) { var existingCallbackNode = root.callbackNode; / / to get root. CallbackNode that old task / / record of the expiration date of the task, check whether there is overdue tasks, it was immediately put it in the root. ExpiredLanes markStarvedLanesAsExpired (root, currentTime); // To check whether the task is expired, put the expired task into root.expiredLanes, RenderLanes var nextLanes = getNextLanes(root, root === workInProgressRoot? workInProgressRootRenderLanes : NoLanes); If (nextLanes === NoLanes) {// If the render priority is empty, no scheduling is required if (existingCallbackNode! == null) { cancelCallback(existingCallbackNode); } root.callbackNode = null; root.callbackPriority = NoLane; return; } var newCallbackPriority = getHighestPriorityLane(nextLanes); Var existingCallbackPriority = root.callbackPriority; // If the priority of the old and new tasks is the same, If (existingCallbackPriority === newCallbackPriority) {{if (existingCallbackNode == null && existingCallbackPriority ! == SyncLane) { error('Expected scheduled callback to exist. This error is likely caused by a bug in React. Please file an issue.'); } } return; } if (existingCallbackNode ! CancelCallback (existingCallbackNode) {// Cancel the old task and cancel the old task. } var newCallbackNode; If (newCallbackPriority === SyncLane) {if (newCallbackPriority === SyncLane) {if (newCallbackPriority === SyncLane) { The traditional synchronous rendering and overdue task will go here if (root) tag = = = LegacyRoot) {scheduleLegacySyncCallback (performSyncWorkOnRoot. Bind (null, root)); } else { scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root)); } { // Flush the queue in a microtask. scheduleMicrotask(flushSyncCallbacks); } newCallbackNode = null; } else {return Scheduler priority based on task priority var schedulerPriorityLevel; switch (lanesToEventPriority(nextLanes)) { case DiscreteEventPriority: schedulerPriorityLevel = ImmediatePriority; break; case ContinuousEventPriority: schedulerPriorityLevel = UserBlockingPriority; break; case DefaultEventPriority: schedulerPriorityLevel = NormalPriority; break; case IdleEventPriority: schedulerPriorityLevel = IdlePriority; break; default: schedulerPriorityLevel = NormalPriority; break; } // After calculating the scheduling priority, NewCallbackNode = scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root)); } // Update the task priority and task on root so that root can be obtained when scheduling next time. CallbackPriority = newCallbackPriority; root.callbackNode = newCallbackNode; }Copy the code

The ensureRootIsScheduled function is actually a key logic that incorporates queue-jumping and task starvation issues with high priority tasks under the task scheduling plane. Take a closer look at react’s task update handling, collection, and rescheduling.

At the end

The process of the Render stage of React must not only contain the content described in this article. The work of the render stage can be divided into the “recursive” stage and the “return” stage. BeginWok is executed in the “pass” stage, and completeWork is executed in the “return” stage. This paper makes an initialization is rendering process overview, the space is limited, interested students can go to react.iamkasong.com/process/rec… Learning, framework learning, combined with the source more fragrant oh.

Refer to the article

  • Reveal the React technology – react.iamkasong.com/process/rec…
  • React source code: -juejin.cn/post/684490…
  • The react scheduler to post – zhuanlan.zhihu.com/p/110161396

Like to pay attention to us!