Some students write react for a long time but don’t know how it works internally. For example, they don’t know what happens when a page is initialized. After reading this article, you will know what happens internally when a page is mounted on react

Goal: What does reactdom.render do from 0 to 1 after reading it

The preparatory work

The preparation need to react to website to download a copy of the source code, after which I will specify in the space below is a file which one line of code that does something specific, you can download the source code control according to oneself the articles to read the source code, in the process of reading may have some pain, but the result is good, The React source code may be a bit difficult to interpret, but that’s normal because it was written by a top-notch programmer

1. First go to the following place to download the source code, so that you do not see the same source code so specify tag17.0.2 source code to read

2. The following are the relevant files and directories that we are going to look at are some of the core parts

  • react-domThis is where the core files are exposed for exampleRender, createPortal, hydrateSuch as core API
  • react-reconcilerCoordination between nodes is mainly done here, including the creation of nodes and assembly, submission and between nodesdiffCompare updates and other operations
  • schedulerScheduling between tasks takes place primarily in this file

start

Will separate modules according to the files below describe what inside have done, will be in the form of capture and add comments to display, then I think don’t need to explain the inside will be folded or delete lest affect reading, if everyone too lazy to download the source code to read, can read twice this article in the mind have a ballpark

render

Let’s go straight to the Render method, which is our external call readtdom.render

File directory react-dom -> SRC -> client -> ReactDOMLegacy

  • Render takes three arguments

    • The first one was sent in by usJSXObject is what we always sayVirtal DOMSo here it goes straight to PIVirtal DOMBecause inwebpackIn the boot package, it’s compiled like this
    • The second is the container in which the render element is loaded<div id='root'></div>
    • The third one is a callback function that gets executed when the application has been mounted, something like thiswindow.onload, monitoring application mount completion
  • renderMethod is calledlegacyRenderSubtreeIntoContainerThe function is athydrateThere are also calls in there that are rendered on the server side, so let’s see what’s going on inside this method

legacyRenderSubtreeIntoContainer

File directory react-dom -> SRC -> client -> ReactDOMLegacy

  • The first thing this method does is determine if there is root if no indication is the first render

    // Initial mount should not be batched.
    unbatchedUpdates(() = > {
      // First render nested unbatchedUpdates go non-batch updates
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
    Copy the code

    The outer layer makes the current environment a non-batch update phase by calling the unbatchedUpdates method

  • If there is root prove to be the update phase for batch update rendering

    // Update
    // Perform batch update tasks
    updateContainer(children, fiberRoot, parentComponent, callback);
    Copy the code

    You can see that there is no nested unbatchedUpdates method on the outer layer, indicating that the current batch update process is going through

  • Now let’s look at what variables are defined inside the unbatchedUpdates method, because today we’re going to focus on the initialization process, okay

unbatchedUpdates

File directory react – the reconciler – > SRC – > ReactFiberWorkLoop. Old. Js

  • Set the current environment to non-batch update

    const prevExecutionContext = executionContext;
    // Indicates that the BatchedContext location in the current context is not batch updated
    executionContext &= ~BatchedContext;
    // Set it to non-batch update on executionContext
    executionContext |= LegacyUnbatchedContext;
    Copy the code
  • We’re going to do the fn method and we’re going to do the updateContainer

    try {
      return fn(a);
    } finally{}Copy the code
  • Now let’s look at what’s going on inside the updateContainer method

updateContainer

File directory react – the reconciler – > SRC – > ReactFiberWorkLoop. Old. Js

  • The updateContainer method is used to perform the update task and receives four parameters

    • The node to be updated for the first element
    • The second container is the container element
    • The third parentComponent is the last node and is mainly used during the update phase
    • The fourth callback argument is the notification method after completion
  • The first step is to create an UPDATE node that will be mounted to Fiber for subsequent DIff updates

    // Create the update task Fiber node
    const update = createUpdate(eventTime, lane);
    ​
    // Mount the JSX object to payload
    update.payload = {element};
    Copy the code
  • The createUpdate method internally generates the Fiber node and returns it

    export function createUpdate(eventTime: number, lane: Lane) :Update< * >{
      const update: Update<*> = {
        eventTime,
        lane,
    ​
        tag: UpdateState,
        payload: null.callback: null.next: null};return update;
    }
    Copy the code
  • The second step is to mount the task to the Fiber node

    // Mount the task to the Fiber node for later updates
    enqueueUpdate(current, update);
    Copy the code
  • The enqueueUpdate method mounts internally

    export function enqueueUpdate<State> (fiber: Fiber, update: Update<State>) {
      const updateQueue = fiber.updateQueue;
      // This occurs when the destruction period function is executed
      if (updateQueue === null) {
        // Only occurs if the fiber has been unmounted.
        return;
      }
    ​
      const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;
      const pending = sharedQueue.pending;
      if (pending === null) {
        // This is the first update. Create a circular list.
        update.next = update;
      } else {
        update.next = pending.next;
        pending.next = update;
      }
      sharedQueue.pending = update;
    }
    Copy the code
  • The third step is to update the fiber node and then scheduleUpdateOnFiber is a big file

scheduleUpdateOnFiber

File directory react – the reconciler – > SRC – > ReactFiberWorkLoop. Old. Js

  • This method mainly looks at the logic of the red label

    if (
      // Check whether the current environment is batch update(executionContext & LegacyUnbatchedContext) ! == NoContext &&// Check whether the current environment is the initialization environment
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // Initializing and not batch updating cases will come in
      schedulePendingInteractions(root, lane);
    ​
      // Start mounting the node
      performSyncWorkOnRoot(root);
    }
    Copy the code
  • The performSyncWorkOnRoot method calls renderRootSync and that’s where the core logic is

File directory react – the reconciler – > SRC – > ReactFiberWorkLoop. Old. Js

renderRootSync

File directory react – the reconciler – > SRC – > ReactFiberWorkLoop. Old. Js

  • This method in do while mainly commits the task in a loop, which is performed in workLoopSync

    do {
      try {
        workLoopSync();
        break;
      } catch(thrownValue) { handleError(root, thrownValue); }}while (true);
    Copy the code

workLoopSync

File directory react – the reconciler – > SRC – > ReactFiberWorkLoop. Old. Js

  • WorkInProgress is a variable stored globally to perform the current task

  • Let’s move onperformUnitOfWork

performUnitOfWork

File directory react – the reconciler – > SRC – > ReactFiberWorkLoop. Old. Js

  • So I’m going to callbeginWorkLet’s just look at itbeginWorkWhat’s going on inside

beginWork

File directory react – the reconciler – > SRC – > ReactFiberBeginWork. Old. Js

  • This is a big file and I’ll separate out what we need to see

    // Compare the old and new props
    // Compare the context before and after
    // If there is one set to true for didReceiveUpdate
    if( oldProps ! == newProps || hasLegacyContextChanged() ||// Force a re-render if the implementation changed due to hot reload:(__DEV__ ? workInProgress.type ! == current.type :false)
    ) {
      didReceiveUpdate = true; / /! Set to true if necessary
    } else if(! includesSomeLane(renderLanes, updateLanes)) { didReceiveUpdate =false;/ /! Set to false if no update is required
    }
    Copy the code
  • Below is the workInProgress tag to determine the different tasks to perform

     switch (workInProgress.tag) {
        case LazyComponent: {
          const elementType = workInProgress.elementType;
          return mountLazyComponent(
            current,
            workInProgress,
            elementType,
            updateLanes,
            renderLanes,
          );
        }
        case FunctionComponent: {
          const Component = workInProgress.type;
          const unresolvedProps = workInProgress.pendingProps;
          const resolvedProps =
            workInProgress.elementType === Component
              ? unresolvedProps
              : resolveDefaultProps(Component, unresolvedProps);
          return updateFunctionComponent(
            current,
            workInProgress,
            Component,
            resolvedProps,
            renderLanes,
          );
        }
         // Update the class component
        case ClassComponent: {
          const Component = workInProgress.type;
          const unresolvedProps = workInProgress.pendingProps;
          const resolvedProps =
            workInProgress.elementType === Component
              ? unresolvedProps
              : resolveDefaultProps(Component, unresolvedProps);
          return updateClassComponent(
            current,
            workInProgress,
            Component,
            resolvedProps,
            renderLanes,
          );
        }
        case HostRoot:
          return updateHostRoot(current, workInProgress, renderLanes);
        case HostComponent:
          return updateHostComponent(current, workInProgress, renderLanes);
        case HostText:
          return updateHostText(current, workInProgress);
        case SuspenseComponent:
          return updateSuspenseComponent(current, workInProgress, renderLanes);
        case HostPortal:
          return updatePortalComponent(current, workInProgress, renderLanes);
      }
    Copy the code
  • The switch method here removes a part of it and leaves behind a bit of the usual, but today we’ll use ClassComponent as an example to see how the implementation is mounted internally. The ClassComponent case calls updateClassComponent, right

updateClassComponent

File directory react – the reconciler – > SRC – > ReactFiberBeginWork. Old. Js

This focuses on the resumeMountClassInstance method and the finishClassComponent method

resumeMountClassInstance

File directory react – the reconciler – > SRC – > ReactFiberClassComponent. Old. Js

  • ResumeMountClassInstance: resumeMountClassInstance: resumeMountClassInstance: resumeMountClassInstance: resumeMountClassInstance: resumeMountClassInstance: resumeMountClassInstance: resumeMountClassInstance

    const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
    const hasNewLifecycles =
          typeof getDerivedStateFromProps === 'function' ||
          typeof instance.getSnapshotBeforeUpdate === 'function';
    Copy the code

This is a check to see if there are two life cycles and if there are they are executed below

  • 2. Take a look at the following logic for handling state

    const oldState = workInProgress.memoizedState;
    let newState = (instance.state = oldState);
    processUpdateQueue(workInProgress, newProps, instance, renderLanes);
    newState = workInProgress.memoizedState;
    Copy the code

Here processUpdateQueue does a state merge internally and returns a new merged state, which is then mounted below

  • 3. Determine whether the lifecycle exists on the component

    if( unresolvedOldProps === unresolvedNewProps && oldState === newState && ! hasContextChanged() && ! checkHasForceUpdateAfterProcessing() ) {if (typeof instance.componentDidUpdate === 'function') {
        if( unresolvedOldProps ! == current.memoizedProps || oldState ! == current.memoizedState ) { workInProgress.flags |= Update; }}if (typeof instance.getSnapshotBeforeUpdate === 'function') {
        if( unresolvedOldProps ! == current.memoizedProps || oldState ! == current.memoizedState ) { workInProgress.flags |= Snapshot; }}return false;
    }
    Copy the code

    Determine if there is a lifecycle in the component and note it in the current environment

  • 4. Look at the getDerivedStateFromProps method

    if (typeof getDerivedStateFromProps === 'function') {
      applyDerivedStateFromProps(
        workInProgress,
        ctor,
        getDerivedStateFromProps,
        newProps,
      );
      newState = workInProgress.memoizedState;
    }
    Copy the code

    This method is new to React and can be used to initialize state

  • 5. Code behind the mysterious ShouldComponentUpdate method

    const shouldUpdate =
          checkHasForceUpdateAfterProcessing() ||
          checkShouldComponentUpdate(
            workInProgress,
            ctor,
            oldProps,
            newProps,
            oldState,
            newState,
            nextContext,
          );
    if (shouldUpdate) {
      if (
        !hasNewLifecycles &&
        (typeof instance.UNSAFE_componentWillMount === 'function' ||
         typeof instance.componentWillMount === 'function')) {if (typeof instance.componentWillMount === 'function') {
          instance.componentWillMount();
        }
        if (typeof instance.UNSAFE_componentWillMount === 'function') { instance.UNSAFE_componentWillMount(); }}if (typeof instance.componentDidMount === 'function') { workInProgress.flags |= Update; }}else {
      if (typeof instance.componentDidMount === 'function') {
        workInProgress.flags |= Update;
      }
      workInProgress.memoizedProps = newProps;
      workInProgress.memoizedState = newState;
    }
    Copy the code

This checks if shouldComponentUpdate returns true and if PureReactComponent is used internally and then executes a different lifecycle, Finally, reassign memoizedProps and memoizedState to workInProgres because they will be used in the later render

  • 6. The end of this method assigns props, state, and context to properties on the instance. Is that why you can call these things in components with this

    instance.props = newProps;
    instance.state = newState;
    instance.context = nextContext;
    Copy the code

finishClassComponent

File directory react – the reconciler – > SRC – > ReactFiberBeginWork. Old. Js

  • I’m going to focus on the inside herereconcileChildrenMethod, which performs node reconciliation internally

reconcileChildren

File directory react – the reconciler – > SRC – > ReactFiberBeginWork. Old. Js

export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes,
) {
  // Current for null proves to be the first render to go below this if, mountChildFibers
  if (current === null) {
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } else{ workInProgress.child = reconcileChildFibers( workInProgress, current.child, nextChildren, renderLanes, ); }}Copy the code

Moving on to mountChildFibers, this method calls a factory function called ChildReconciler

// The ChildReconciler method is called only with different input parameters
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);
Copy the code

ChildReconciler

File directory React-Reconciler -> SRC -> reactChildFibre.old.js

  • This method is a factory function contains a 20 several method to a total of more than one thousand lines, which mainly includes the processing updates and comparing various types of nodes, if you are interested can go to take a look at this method probably follow file directory in more than 270 lines, below I will take out your initialization logic code interpretation

The factory function returns this method with the reconcileChildFibers basically coordinating child nodes

  • The child nodes in the object case are treated separately
// Update according to the corresponding node, for array objects and plain text nodes
const isObject = typeof newChild === 'object'&& newChild ! = =null;
if (isObject) {
  switch (newChild.$$typeof) {
    case REACT_ELEMENT_TYPE:
      return placeSingleChild(
        reconcileSingleElement(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
        ),
      );
    case REACT_PORTAL_TYPE:
      return placeSingleChild(
        reconcileSinglePortal(
          returnFiber,
          currentFirstChild,
          newChild,
          lanes,
        ),
      );
    case REACT_LAZY_TYPE:
      if (enableLazyElements) {
        const payload = newChild._payload;
        const init = newChild._init;
        // TODO: This function is supposed to be non-recursive.
        returnreconcileChildFibers( returnFiber, currentFirstChild, init(payload), lanes, ); }}}Copy the code

Processing of normal text nodes

if (typeof newChild === 'string' || typeof newChild === 'number') {
  return placeSingleChild(
    reconcileSingleTextNode(
      returnFiber,
      currentFirstChild,
      ' ' + newChild,
      lanes,
    ),
  );
Copy the code

Each of these node update methods calls a createFiber method inside to create a fiber node

  • File directoryreact-reconciler -> src ->ReactFiber.old.js
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);
};
Copy the code

FiberNode

React-reconciler -> SRC -> reactFiber.old.js

Looking inside the FiberNode creation node, this method is a base class created by Fiber

Parse the nodes in Fiber

this.tag = tag; // Mark the component type
this.key = key; // The key value of the node will be used in the late diff
this.type = null;// The node type can be a div or other tag, or a function if it is a component
this.stateNode = null; // Real nodes such as div or other tags// Fiber
this.return = null; // The parent node to point to
this.child = null; // Points to the child node
this.sibling = null; // The sibling node to point tothis.memoizedState = null; // Store state after rendering is complete
this.pendingProps = pendingProps; // This is the props after initialization
this.memoizedProps = null; // Perform props after rendering
this.lanes = NoLanes; // Whether the priority of the current node task is to render it, or whether there is a higher priority task
this.alternate = null; // Store the last node. Diff will be judged according to the attributes above the node and the current node attributes
Copy the code

conclusion

Here the initialization source code analysis is over, if you have time to look at the source code file to comb through again, there is a general process in mind, it may take half an hour, but comb through their understanding of the initialization process is certainly a certain improvement