Learn react source code

React source code: React Source code

  1. Share your own learning experience, and hope that students with relevant learning needs can find the address and avoid detours.
  2. Convert the learned content into text output for later review (so most of the text may be mechanically copied, and only a small part is my own understanding)

I have divided this period into the following stages:

  1. Write a simple React by hand according to the tutorial
  2. React call stack to see how React works in general
  3. React source code download, use the React source code analysis tutorial, learn while debugging
  4. Review the tutorial interpretation again and add detailed comments to the React source along with other materials (including but not limited to the official website, other interpretation materials, and videos)
  5. Write a note about the React source code (something in progress)

Once again, this article is intended to be shared with learning managers. If you want to learn the React source code systematically, you can refer to the excellent article at the end of this article

How to write a simple React framework by hand

Install a simple react script on your own. It is recommended to follow the video tutorial below.

React17 source boot camp

According to the video tutorial source

How to debug React source code

  1. First, clone the React source code locally from react. I’m using V17.0.2
# pull code
1. git clone https://github.com/facebook/react.git
CNPM images can also be used
2. git clone https://github.com.cnpmjs.org/facebook/react 
Or use a mirror of the code cloud
3. git clone https://gitee.com/mirrors/react.git
Copy the code
  1. Install dependencies
cd react
yarn
Copy the code
  1. Execute the package command to package react, react-dom, and Scheduler packages into separate files for easy debugging and reading (JDK installation is required during the build process).
yarn build react/index,react/jsx,react-dom/index,scheduler --type=NODE
Copy the code

Tips: If you have trouble building yourself, you can just use what I packed

Packaged address

  1. Create your own React project using CRA
npx create-react-app my-app
Copy the code
  1. Use our packaged JS files in the my-app project
Step1: Run yarn Link in the react directory step2: run yarn link react in the my-app project directory Step3: run yarn link in the react-dom directory Step4: Run yarn Link React -dom in the my-app project directoryCopy the code
  1. Test whether YARN Link takes effect
// Add your own log to react-dom/ CJS /react-dom.development.js

// 1. The entry function called in the application is here
function render(element, container, callback) {
  console.log('React Render function executed');
  if(! isValidContainer(container)) { {throw Error( "Target container is not a DOM element."); }} {var isModernRoot = isContainerMarkedAsRoot(container) && container._reactRootContainer === undefined;

    if (isModernRoot) {
      error('You are calling ReactDOM.render() on a container that was previously ' + 'passed to ReactDOM.createRoot(). This is not supported. ' + 'Did you mean to call root.render(element)? '); }}return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}
Copy the code

Start the My-React project and open the console… As you can see, the react and react-dom packages use our packages instead of node_modules. Now we can debug happily!

React Fiber architecture concept

The latest React architecture can be divided into three layers:

  • Scheduler ———— Schedules the priority of tasks, and high-priority tasks are given priority to enter Reconciler
  • ———— Reconciler is responsible for comparing changes in nodes before and after the Reconciler update (DIff algorithm)
  • Renderer ———— is responsible for rendering nodes that need to change onto the page

Scheduler

The Scheduler package, which has only one core responsibility, is to perform callbacks.

  • thereact-reconcilerProvides a callback function wrapped in a task object.
  • Internally maintain a task queue, with tasks with higher priorities placed first.
  • Cyclic consumption of the task queue until the queue is empty.

Reconciler:

The React-Reconciler package has three core responsibilities:

1. Load the renderer The renderer must implement [` HostConfig ` agreement] (HTTP: / / https://github.com/facebook/react/blob/v17.0.1/packages/react-reconciler/README.md#practical-exa Mples)(e.g. 'react-dom') to ensure that the renderer's API is properly called to generate actual nodes (e.g. 'dom' nodes) when needed. Receives update requests from the 'react-dom' package (initial 'render') and 'react' package (subsequent update 'setState'). The 'fiber' tree construction process is wrapped in a callback function that is passed to the 'scheduler' package for scheduling.Copy the code

React is best known for its interruptible rendering. Is after react16 UNSAFE_componentWillMount UNSAFE_componentWIllReceivePropsrender function to be executed statement cycle phase is not secure, because render stage is interruptible. But! Only in HostRootFiber. Mode = = = ConcurrentRoot | BlockingRoot will open. In Legacy mode, When started with reactdom.render (
, DOM), either the first render or subsequent update will only go into a synchronous working loop, and the reconciliation will not have a chance to break, so the life cycle function will only be called once. So while interruptible rendering has been implemented in Act17, it is, for now, an experimental feature.

Renderer

The React-DOM package has two core responsibilities:

  1. guidereactApplication startup (passReactDOM.render).
  2. implementationHostConfigagreement(The source code is in reactdomhostconfig.js), to be able toreact-reconcilerThe package is constructedfiberTree representation, generating DOM nodes (in the browser), generating strings (SSR).

React’s Fiber architecture works

Double cache Fiber tree

What is double caching: The technique of building in memory and replacing it directly is called double caching.

In React, at most two Fiber trees exist at the same time. The Fiber tree corresponding to the content displayed on the current screen is called the Current Fiber tree, and the Fiber tree being constructed in memory is called the workInProgress Fiber tree.

The Fiber nodes in the current Fiber tree are called current Fiber, and the Fiber nodes in the workInProgress Fiber tree are called workInProgress Fiber, and they are connected via the alternate property.

currentFiber.alternate === workInProgressFiber;
workInProgressFiber.alternate === currentFiber;
Copy the code

The fiberRootNode of the React application replaces the Current tree and workInProgress tree by changing the current pointer to point to different Fiber trees.

When the workInProgress tree is built and rendered by Renderer on the page, the fiberRootNode pointer points to the workInProgress Fiber tree, and the workInProgress tree becomes the Current Fuber tree

Each status update generates a new workInProgress Fiber tree, and DOM updates are completed by replacing current with workInProgress.

When the mount

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      number: 1}}render() {
    const { number } = this.state;
    return (
      <div onClick={()= > {this.setState({number: number+1})}}>
         classComponent: { number}
      </div>
    )
  }
}


ReactDOM.render(
  <App />.document.getElementById('root'))Copy the code
  1. For the first time to performReactDOM.renderWill createfiberRootNode(fiberRoot in source) androotFiber. Among themfiberRootNodeIs the root node of the entire application,rootFiberis<App/>The root node of the component.

Reactdom.render can be called multiple times in our application. There will be multiple RootFibers, but only one fiberRootNode. FiberRootNode always points to the Fiber tree rendered on the current page, the Current Fiber tree

fiberRootNode.current = rootFiber
Copy the code
  1. Let’s go inrenderPhase, returned according to the componentJSXCreate them in sequence in memoryFiber nodeAnd build it all togetherFiber tree, known as theWorkInProgress Fiber tree. (In the figure below, the tree built in memory is on the right and the tree displayed on the page is on the left)

When constructing the workInProgress Fiber tree, it will try to reuse the properties in the existing Fiber nodes in the current Fiber tree, and only rootFiber has corresponding current Fiber (rootFiber. Alternate) in the first screen rendering.

  1. Built on the right in the image aboveWorkInProgress Fiber treeinThe commit phaseAfter rendering to the page. At this timeDOMUpdate it to the right.fiberRootNodethecurrentPointer toWorkInProgress Fiber treeMake it a current tree

Corresponding code:

// commitRootImpl focus on this line of code!! Switch between workInProgress tree and Current tree
root.current = finishedWork;
Copy the code

When the update

  1. So what we’re going to do is callsetStateTrigger the update. This one will open a new oneRender phaseAnd build a new oneWorkInProgress tree

As with mount, the workInProgress Fiber can be created using the node data corresponding to the Current Fiber tree.

This process of deciding whether to reuse is called the Diff algorithm

  1. WorkInProgress Fiber treeinrenderPhase enters after completion of constructioncommitStages render to the page. After rendering, workInProgress FiberTrees turnFor the current FiberThe tree.

React explains high-frequency objects

Fiber object

Look at the data structure, whose type type is defined in reactinternaltypes.js:

export type Fiber = {|
  tag: WorkTag, // Identify the fiber type, generated according to the type of the ReactElemnt component
  key: null | string, // A unique representation of this node for optimization of the diff algorithm
  elementType: any, // Generally, the type of the component is the same as that of the ReactElement component
  type: any, // Generally speaking, this is the same as fiber. ElementType. Special cases such as function, class, or ForwardRef ReactElement for HotReloading are different from fiber.elementType
  stateNode: any, // The local state node associated with fiber (e.g., HostComponent refers to the DOM node corresponding to the fiber node; The root node fiber.stateNode points to FiberRoot; The stateNode of a class node points to an instance of class.
  
  return: Fiber | null.// The parent of this node
  child: Fiber | null.// The first child of this node
  sibling: Fiber | null.// The next child of this node
  index: number, // The subscript of this node
  ref:
    | null
    | (((handle: mixed) = > void) & { _stringRef: ?string, ... })
    | RefObject,
  pendingProps: any, // Props passed from the 'ReactElement' object. Compare with 'fiber.memoizedProps' to see if the properties have changed
  memoizedProps: any, // Attributes used in the last generation of child nodes are kept in memory after the generation of child nodes
  updateQueue: mixed, // A queue that stores state updates. When the state of the current node changes, an update object is created and added to the queue.
  memoizedState: any, // State for output, state for final rendering
  dependencies: Dependencies | null.// This fiber node is dependent on (Contexts, Events) etc
  mode: TypeOfMode, // The binary Bitfield is inherited from the parent node and affects all nodes in the fiber node and its subtree. React is related to the running mode of the React application (ConcurrentMode, BlockingMode, NoMode, etc.).

  // Effect is associated with side effects
  flags: Flags, / / sign
  subtreeFlags: Flags, // Replace firstEffect, nextEffect in 16.x. It is enabled when enableNewReconciler= True is set
  deletions: Array<Fiber> | null.// Store the child nodes to be deleted. It is enabled when enableNewReconciler= True is set

  nextEffect: Fiber | null.// One-way linked list pointing to the next fiber node with side effects
  firstEffect: Fiber | null.// Point to the first fiber node in the side effects list
  lastEffect: Fiber | null.// Points to the last fiber node in the side effects list

  // Priority is related
  lanes: Lanes, // The priority of this fiber node
  childLanes: Lanes, // Priority of the child node
  alternate: Fiber | null.// Point to another fiber in memory. Each updated fiber node appears in pairs in memory (current and workInProgress).

  // Performance statistics (statistics are generated only after enableProfilerTimer is enabled)
  // react-dev-tool uses these time statistics to evaluate performanceactualDuration? : number,// The total time consumed by the update process, the node and the subtreeactualStartTime? : number,// Mark the time when the fiber node starts buildingselfBaseDuration? : number,// This is used to implement the consumption of the latest cost fiber nodetreeBaseDuration? : number,// The sum of the time spent generating subtrees
|};
Copy the code

Update and Update Ue objects

When react triggers a status update, we first create an Update object.

Status update process: Trigger status updates – > create the Update object – > from fiber to root (markUpdateLaneFromFiberToRoot) – > scheduling Update (ensureRootIsScheduled) – > Render phase (performSyncWorkOnRoot or performConcurrentWorkOnRoot) – > the commit phase (commitRoot)

export type Update<State> = {|
  eventTime: number, // The time when the update event was initiated (as a temporary field in 17.0.1, to be removed soon)
  lane: Lane, // Update priority

  tag: 0 | 1 | 2 | 3.// There are 4 types. UpdateState,ReplaceState,ForceUpdate,CaptureUpdate
  payload: any, // Payload, depending on the scenario, can be set to a callback function or object (the first argument in setState)
  callback: (() = > mixed) | null.// The callback function (the second argument in setState)

  next: Update<State> | null.// Points to the next update in the list. Since UpdateQueue is a circular list, the last update.next points to the first update object
|};

// =============== UpdateQueue ==============
type SharedQueue<State> = {|
  pending: Update<State> | null|};export type UpdateQueue<State> = {|
  baseState: State,
  firstBaseUpdate: Update<State> | null.lastBaseUpdate: Update<State> | null.shared: SharedQueue<State>,
  effects: Array<Update<State>> | null|};Copy the code

The fiber object has an Update queue, which is a chained queue. The following diagram illustrates the relationship between the Fiber,Update, and Update objects

Hook object

With the advent of Hook, function functions have the ability to manage state. Since [email protected], Hook syntax has been officially recommended. There are officially 14 Hook types defined.

export type Hook = {|
  memoizedState: any, // Memory state, which is output to the final fiber tree
  baseState: any, // baseState, when Hook. Queue is updated, baseState is also updated
  baseQueue: Update<any, any> | null.The basic state queue assists in the Reconciler stage with state consolidation.
  queue: UpdateQueue<any, any> | null.// Point to an Update queue
  next: Hook | null.// Points to the next Hook object of the function component, so that multiple hooks form a linked list.
|};

// UpdateQueue and Update are to ensure that the Hook object can be updated smoothly, which is different from the above mentioned UpdateQueue and Update (and they are in different files).
type Update<S, A> = {|
  lane: Lane,
  action: A,
  eagerReducer: ((S, A) = > S) | null.eagerState: S | null.next: Update<S, A>, priority? : ReactPriorityLevel, |};//UpdateQueue and Update are to ensure that the Hook object can be updated smoothly, which is different from the above mentioned UpdateQueue and Update (and they are in different files).
type UpdateQueue<S, A> = {|
  pending: Update<S, A> | null.dispatch: (A= > mixed) | null.lastRenderedReducer: ((S, A) = > S) | null.lastRenderedState: S | null|};Copy the code

See the React diagram for more details on high-frequency objects

The React project startup process

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

In the previous example of how the Fiber architecture works, we mentioned that fiberRootNode and rootFiber objects are created during mount. We also created a ReactDOMRoot object and called its Render method to start rendering our React program.

Render phase

The main task of the Render phase is to create the Fiber node and build the Fiber tree.

Render phase began in performSyncWorkOnRoot or performConcurrentWorkOnRoot method call. This depends on whether the update is synchronous or asynchronous (since we called the Render method when rendering, the default is synchronous updates).

// performSyncWorkOnRoot calls this method
function workLoopSync() {
  while(workInProgress ! = =null) { performUnitOfWork(workInProgress); }}Copy the code

PerformUnitOfWork’s work can be divided into two parts: “recursion” and “return”.

Before building the Fiber tree, I want to use a ‘dynasty’ story to describe depth-first traversal. It is said that when the emperor passes away (the current Fiber node is processed), he will pass the throne to the prince (the first son is also the child in Fiber), if there is no prince, he will pass the throne to his brother (sibling is also the sibling in Fiber node), if he can’t find the sibling, he will look up to his father’s brother. When the person found is the first emperor, it means that there is no successor. The dynasty will be destroyed (the Fiber tree traversal will end)

The “pass” phase

First, the downward depth-first traversal starts from rootFiber. Call the beginWork method (explained in more detail later) for each traversed Fiber node. This method creates child nodes from the incoming Fiber node and connects the two Fiber nodes. When traversed to the leaf node, the “return” phase is entered

“Return” stage

The completeWork is called in the “homing” phase to handle the Fiber node. When a Fiber node completes completeWork(more on that later), if it has a sibling Fiber node (i.e. == null), will enter the “pass” phase of its sibling Fiber.

If no sibling Fiber exists, the parent Fiber will enter the “return” phase.

The Recursion and return phases are staggered until return to rootFiber. At this point, the render phase is over

example
function App() {
  return (
    <div>
      i am
      <span>KaSong</span>
    </div>
  )
}

ReactDOM.render(<App />.document.getElementById("root"));
Copy the code

The Render phase is executed sequentially

1. rootFiber beginWork
2. App Fiber beginWork
3. div Fiber beginWork
4. "i am" Fiber beginWork
5. "i am" Fiber completeWork
6. span Fiber beginWork
7. span Fiber completeWork
8. div Fiber completeWork
9. App Fiber completeWork
10. rootFiber completeWork
Copy the code

In render phasebeginWork

/ * * *@desc The job of the beginWork is to pass in the current Fiber node, create sub-fiber nodes, and mark the corresponding Fiber with effectTag * according to the Diff algorithm@params Current The Fiber node corresponding to the current component in the last update, i.e. Workinprogress.alternate *@params WorkInProgress The Fiber node * corresponding to the current component@params RenderLanes Priority is related */
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) :Fiber | null {}Copy the code
An overview of the process
  • Current: indicates the current componentFiber nodeIn the last updateFiber nodeIs the node to which fiberRootNode points
  • WorkInProgress: corresponding to the current componentFiber node(Joint result of JSX and State)
  • RenderLanes: Priority dependent

For performance reasons, it’s not possible to recreate Fiber nodes every time you re-render React, and you’ve probably heard of the Diff algorithm. Therefore, in the beginWork, it is necessary to distinguish the first rendering (mount) or update (update) to reduce unnecessary rendering.

React uses a dual cache mechanism, but the current tree does not exist at the time of the first rendering.

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes
) :Fiber | null {

  // Update: If current exists, there may be optimized path, can reuse current (i.e. last updated Fiber node)
  if(current ! = =null) {
    / /... omit

    / / reuse current
    return bailoutOnAlreadyFinishedWork(
      current,
      workInProgress,
      renderLanes,
    );
  } else {
    didReceiveUpdate = false;
  }

  // Create different sub-fiber nodes according to the tag
  switch (workInProgress.tag) {
    case IndeterminateComponent: 
      / /... omit
    case LazyComponent: 
      / /... omit
    case FunctionComponent: 
      / /... omit
    case ClassComponent: 
      / /... omit
    case HostRoot:
      / /... omit
    case HostComponent:
      / /... omit
    case HostText:
      / /... omit
    / /... Omit other types}}Copy the code
When the mount

As you can see from the code above, when current=== NULL is used to determine the first rendering, we need to create different content based on the tag attribute in Fiber (so the first screen rendering is actually time-consuming, which is also a problem for single-page apps).

When the update

As you can see, didReceiveUpdate === false (i.e. you can reuse the subfiber from the previous update without creating a new subfiber)

  1. oldProps === newProps && workInProgress.type === current.type, i.e.,propswithfiber.typeThe same
  2. ! includesSomeLane(renderLanes, updateLanes)That is, the current Fiber node has insufficient priority

Take the updateFunctionComponent for example, if we make a series of judgments and find that the Fiber node is reusable. Then, you don’t need to spend a lot of operation to diff and just reuse existing Fiber nodes.

function updateFunctionComponent {...if(current ! = =null && !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  } // React DevTools reads this flag.
}
Copy the code
reconcileChildren

This is a core part of the Reconciler module, as the function name reveals.

  • formountThe component that he will createThe child Fiber node
  • forupdateComponent, which will correspond to the current component since the last updateFiberNode comparison (also known as Diff algorithm), the result of comparison to generate newFiber node(reuse)
The reconcileChildren reconcilement function is an important piece of logic in the 'updateXXX' function, which produces child nodes down and sets' fiber.flags'. When the 'fiber' node is first created, it has no comparison object. * The 'ReactElement' object needs to be compared with the 'old Fiber' object to determine whether the 'old Fiber' object needs to be reused. */
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes
) {
  if (current === null) {
    // For mount components
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } else {
    // For update componentsworkInProgress.child = reconcileChildFibers( workInProgress, current.child, nextChildren, renderLanes, ); }}Copy the code

For the DIff algorithm, we will not expand it in detail here, but if you want to know more about it, you can refer to the DIff algorithm

The Flags (react16 effectTag)

As we know, the render phase works in memory, and the Renderer needs to be notified to perform specific DOM operations when the work is done. You need to perform the DOM, and what kind of DOM operations do you perform? You need to follow fiber.flags.

Such as:

// DOM needs to be inserted into the page
export const Placement = / * * / 0b00000000000010;
// DOM needs to be updated
export const Update = / * * / 0b00000000000100;
// The DOM needs to be inserted into the page and updated
export const PlacementAndUpdate = / * * / 0b00000000000110;
// DOM needs to be deleted
export const Deletion = / * * / 0b00000000001000;
Copy the code
function markUpdate(workInProgress) {
  workInProgress.flags |= Update; // Put the Update tag (effectTag in react16), beginWork stage or completeWork stage?
}
Copy the code
updateClassComponent{
  ...
  // Place the Placement label
  workInProgress.flags |= Placement;
}
Copy the code

In render phasecompleteWork

/** * Put some finishing touches on the last node diff after it completes. * 1. Place the name of the property to be updated in the updateQueue property of the Fiber node * 2. Generate EffectList (subtreeFlags) * /
function completeWork(current, workInProgress, renderLanes) {
  var newProps = workInProgress.pendingProps;

  switch(workInProgress.tag) { ... }}Copy the code
An overview of the process

In the beginWrok, we mentioned that after the beginWork is executed, a sub-fiber node will be created, and the Fiber node may have effectTags.

Similar to beginWork, completeWork calls different processing logic for different fiber.tag.

function completeWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
) :Fiber | null {
  const newProps = workInProgress.pendingProps;

  switch (workInProgress.tag) {
    case IndeterminateComponent:
    case LazyComponent:
    case SimpleMemoComponent:
    case FunctionComponent:
    case ForwardRef:
    case Fragment:
    case Mode:
    case Profiler:
    case ContextConsumer:
    case MemoComponent:
      return null;
    case ClassComponent: {
      / /... omit
      return null;
    }
    case HostRoot: {
      / /... omit
      updateHostContainer(workInProgress);
      return null;
    }
    case HostComponent: {
      / /... omit
      return null;
    }
  / /... omit
Copy the code

We focus on the HostComponent (the Fiber node corresponding to the native DOM component) that is required for page rendering.

Processing HostComponent

Just like beginWork, we call current === null? Determine whether to mount or update.

When the Update

When updating, Fiber nodes already have corresponding DOM nodes, so there is no need to generate DOM nodes. The main thing to do is to handle props, such as:

  • onClick,onChangeWait for the callback function to register
  • To deal withstyle prop
  • To deal withDANGEROUSLY_SET_INNER_HTML prop
  • To deal withchildren prop

Inside the updateHostComponent, be processed props will be assigned to workInProgress. UpdateQueue, and eventually will be rendered on the page in the commit phase.

workInProgress.updateQueue = (updatePayload: any);
Copy the code

UpdatePayload is an array whose even index values are the changing prop key and odd index values are the changing Prop value.

When the Mount

As you can see, there are three main logic for mounting:

  • forFiber nodeGenerate the correspondingDOM node
  • The sons ofDOM nodeInsert just generatedDOM nodeIn the
  • withupdateIn the logicupdateHostComponentSimilar treatmentpropsThe process of

The commit phase

The commitRoot method is the starting point for the COMMIT phase. FiberRootNode is used as the pass parameter.

In rootFiber. FirstEffect, a one-way linked list of the Fiber nodes that need to perform the side effects is saved, and the changes in the updateQueue of these Fiber nodes are saved.

The DOM operations corresponding to these side effects are performed during the COMMIT phase.

In addition, some lifecycle hooks (such as componentDidXXX, useEffect) need to be executed in the COMMIT phase.

The main work of the COMMIT phase (i.e., Renderer’s workflow) is divided into three parts:

  • Before mutation phase (before DOM operation)
  • Mutation stage (DOM operation performed)
  • Layout stage (after DOM manipulation)

Before mutation stages

Before mutation phase code is very short, the whole process is ergodic effectList commitBeforeMutationEffects function called and processing.

/** * 1. Handle DOM node render/delete autoFocus, blur logic * 2. Call getSnapshotBeforeUpdate lifecycle hook * 3. Scheduling useEffect * /
function commitBeforeMutationEffects(root, firstChild) {
  // 1. Handle the autoFocus and blur logic after DOM node rendering/deletion
  focusedInstanceHandle = prepareForCommit(root.containerInfo);
  nextEffect = firstChild;
  // Call getSnapshotBeforeUpdate useEffect life cycle function
  commitBeforeMutationEffects_begin(); // We no longer need to track the active instance fiber

  var shouldFire = shouldFireAfterActiveInstanceBlur;
  shouldFireAfterActiveInstanceBlur = false;
  focusedInstanceHandle = null;
  return shouldFire;
}
Copy the code
getSnapshotBeforeUpdate

Speaking of the getSnapshotBeforeUpdate lifecycle function, we have to remember the UNSAFE_ prefix added to the componentWillXXX hook. Since the Render phase is interruptible/restart, these UNSAFE lifecycle functions may be executed repeatedly, but the COMMIT phase is synchronous and therefore does not encounter duplication.

Mutation stages

Similar to the before mutation phase, the mutation phase also iterates through the effectList, executing the function. CommitMutationEffects are performed here.

function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
  / / traverse effectList
  while(nextEffect ! = =null) {

    const effectTag = nextEffect.effectTag;

    // Reset the literal node according to the ContentReset effectTag
    if (effectTag & ContentReset) {
      commitResetTextContent(nextEffect);
    }

    / / update the ref
    if (effectTag & Ref) {
      const current = nextEffect.alternate;
      if(current ! = =null) { commitDetachRef(current); }}// Separate according to effectTag
    const primaryEffectTag =
      effectTag & (Placement | Update | Deletion | Hydrating);
    switch (primaryEffectTag) {
      / / insert the DOM
      case Placement: {
        commitPlacement(nextEffect);
        nextEffect.effectTag &= ~Placement;
        break;
      }
      // Insert and update the DOM
      case PlacementAndUpdate: {
        / / insert
        commitPlacement(nextEffect);

        nextEffect.effectTag &= ~Placement;

        / / update
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      // SSR
      case Hydrating: {
        nextEffect.effectTag &= ~Hydrating;
        break;
      }
      // SSR
      case HydratingAndUpdate: {
        nextEffect.effectTag &= ~Hydrating;

        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      / / update the DOM
      case Update: {
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      / / remove the DOM
      case Deletion: {
        commitDeletion(root, nextEffect, renderPriorityLevel);
        break; } } nextEffect = nextEffect.nextEffect; }}Copy the code

CommitMutationEffects iterates through the effectList, performing the following three actions on each Fiber node:

  1. According to theContentReset effectTagReset text node
  2. updateref
  3. According to theeffectTagRespectively, whereeffectTagIncluding,Placement | Update | Deletion | Hydrating)
Placement effect

When a Fiber node has a Placement effectTag, it means that the DOM node corresponding to the Fiber node needs to be inserted into the page.

The method called is commitPlacement

We can take a look at the native DOM operation that was eventually called

function appendChildToContainer(container, child) {
  var parentNode;

  if (container.nodeType === COMMENT_NODE) {
    parentNode = container.parentNode;
    parentNode.insertBefore(child, container); // Familiar native DOM manipulation
  } else {
    parentNode = container;
    parentNode.appendChild(child); // Familiar native DOM manipulation}}Copy the code
Update effect

When a Fiber node has an Update effectTag, it means that the Fiber node needs to be updated. CommitWork is called, which is processed separately according to fiber. tag.

When fiber. The tag for FunctionComponent, invokes the commitHookEffectListUnmount. This method iterates through the effectList and executes the destruction functions of all useLayoutEffect hooks.

useLayoutEffect(() = > {  / /... Return () => {//... This is the destruction function}})
Copy the code

When fiber.tag is HostComponent, commitUpdate is called.

Finally, the content of the Update Ue assigned to the Fiber node in the Render phase completeWork (Opens new Window) will be rendered on the page in updateDOMProperties.

for (let i = 0; i < updatePayload.length; i += 2) {
  const propKey = updatePayload[i];
  const propValue = updatePayload[i + 1];

  / / processing style
  if (propKey === STYLE) {
    setValueForStyles(domElement, propValue);
  / / DANGEROUSLY_SET_INNER_HTML processing
  } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
    setInnerHTML(domElement, propValue);
  / / deal with children
  } else if (propKey === CHILDREN) {
    setTextContent(domElement, propValue);
  } else {
  // Handle the remaining propssetValueForProperty(domElement, propKey, propValue, isCustomComponentTag); }}Copy the code
Deletion effect

If the Fiber node contains Deletion effectTag, the DOM node corresponding to the Fiber node needs to be deleted from the page. The method called is commitDeletion.

This method does the following:

  1. Recursive callsFiber nodeAnd the sons ofFiber nodeIn thefiber.tagforClassComponentthecomponentWillUnmountThe lifecycle hook is removed from the pageFiber nodeThe correspondingDOMnode
  2. Unbundling ref
  3. Schedule the useEffect destruction function

Layout stage

This stage is called layout because the code for this stage is executed after DOM rendering is complete (the mutation stage is complete).

Like the previous two phases, the Layout phase iterates through the effectList, executing functions.

The specific function that is executed is commitLayoutEffects. CommitLayoutEffects do two main things

  1. commitLayoutEffectOnFiberCall the lifecycle hook andhookRelated operations)
  2. CommitAttachRef (assigned ref)
root.current = finishedWork;

nextEffect = firstEffect;
do {
  try {
    commitLayoutEffects(root, lanes);
  } catch(error) { invariant(nextEffect ! = =null."Should be working on an effect."); captureCommitPhaseError(nextEffect, error); nextEffect = nextEffect.nextEffect; }}while(nextEffect ! = =null);

nextEffect = null;
Copy the code
commitLayoutEffectOnFiber

CommitLayoutEffectOnFiber method according to the fiber. The tag for different types of nodes, respectively.

For classComponent, he calls componentDidMount or componentDidUpdate, depending on whether it’s mount or Update

The this.setState that triggers the status update is also called at this point if the second parameter callback is assigned.

For FunctionComponent and related types, it calls the callback function of useLayoutEffect hook to schedule useEffect destruction and callback functions

For HostRoot, rootFiber, the callback function will also be called if the third parameter is assigned.

ReactDOM.render(<App />.document.querySelector("#root"), function() {
  console.log("i am mount~");
});
Copy the code
commitAttachRef

The second thing that commitLayoutEffects does is commitAttachRef.

The code logic is simple: get the DOM instance and update the REF.

Switching between current Fiber trees

The workInProgress Fiber tree will change to the Current Fiber tree after rendering in the COMMIT phase. This line of code toggles the Current Fiber tree that fiberRootNode points to.

Update process of the React project

The following example uses classComponnt as an example. Call stack when setState

First let’s look at the definition of setState. A setState method hangs on the Component prototype

Component.prototype.setState = function (partialState, callback) {  if(! (typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {{throw Error( "setState(...) : takes an object of state variables to update or a function which returns an object of state variables."); }}this.updater.enqueueSetState(this, partialState, callback, 'setState'); };Copy the code

enqueueSetState

  1. The component instance will be passed first_reactInternalsgetfiberNode.
  2. And then create thissetStatetheupdateobject
  3. With the mountThe update objectfiberNode scheduling update (executerender.commit)
enqueueSetState: function (inst, payload, callback) {
    var fiber = get(inst);  // Get the fiber node of the current component.var update = createUpdate(eventTime, lane);  // Create the update object for this update
    enqueueUpdate(fiber, update);  // Attach the Update object to the fiber node
    var root = scheduleUpdateOnFiber(fiber, lane, eventTime); // Schedule updates
  },
Copy the code

enqueueUpdate

New updates are stored as a one-way loop on shared.pending (the loop is clipped when calculating state and linked to lastBaseUpdate, Calculating state is done in the getStateFromUpdate in the Render phase.)

Here is the code to build a circular list and mount it under shared.pending

{
    var pending = sharedQueue.pending;

    if (pending === null) {
      // This is the first update. Create a circular list.
      update.next = update;  // Build a one-way circular list
    } else {
      // console.log(' if (pending ! == null) { ', pending); // Batch update
      update.next = pending.next; // Build a one-way circular list
      pending.next = update; // Build a one-way circular list
    }

    sharedQueue.pending = update;
  }
Copy the code

markUpdateLaneFromFiberToRoot

Since we need to know which rootFiber to update when we schedule updates (there may be d more rootFiber when reactdom.render executes multiple times), We need to get to the current rootFiber through markUpdateLaneFromFiberToRoot.

ensureRootIsScheduled

To schedule updates, we can see that performSyncWorkOnRoot is not executed immediately, but is placed in a callback and eventually stored in syncQueue (1. SetState is not immediately updated, 2. And when setState is repeated, the updated content will be saved first and updated in batches at a time.)

if (newCallbackPriority === SyncLanePriority) {
    // Special case: Sync React callbacks are scheduled on a special
    // internal queue
    // The task has expired and the render phase needs to be executed synchronously
    scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    newCallbackNode = null;
  } else if (newCallbackPriority === SyncBatchedLanePriority) {
    // Updates after setState are not performed synchronously immediately, but according to the callback
    newCallbackNode = scheduleCallback(ImmediatePriority, performSyncWorkOnRoot.bind(null, root)); 
  } else {
    // Execute the Render phase asynchronously based on task priority
    var schedulerPriorityLevel = lanePriorityToSchedulerPriority(newCallbackPriority);
    newCallbackNode = scheduleCallback(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
  }
Copy the code

After performSyncWorkOnRoot, it’s back to the Render phase and commit phase (calculating the new state logic is in getStateFromUpdate, but this is for the Render phase and won’t be explained here).

Summary of status update process

Trigger status updates (according to different scene called methods) | | v create the Update object | | v from fiber to root (`markUpdateLaneFromFiberToRoot`Scheduling update () | | v`ensureRootIsScheduled`Render phase () | | v`performSyncWorkOnRoot``performConcurrentWorkOnRoot`The commit phase () | | v`commitRoot`)Copy the code

Tips: Is setState synchronous or asynchronous? What is the result of setState multiple times? Why is setState in setTimeout executed immediately?

My React source code comments

The above are some of my sketchy thoughts after reading the React source code recently. Of course, this article only lists a small part of the content. For detailed comments, please refer to the detailed comments

React source code to solve the problem

Bug caused by using array subscripts as keys

// It looks like this, since the array subscript is used as the key
/ / react in diff when judging componenet. The type is the same as the key, you can reuse the previous node, at best is an Update, so in this way, componentDidMount/useState end of the initial value is invalid
list.map((item, key) = > (
  <Component key={key} dataSource={item}/>
))
Copy the code

Q&A

  1. Is setState synchronous or asynchronous?

    React rendering is divided into synchronous rendering and asynchronous rendering. The reactdom. render we currently use is a synchronous render. In synchronous rendering scenarios, setState is updated asynchronously with React control. But there is an update, such as setTimeout and setInterval, addEventLister native events such as (out of the react of controls, the new rev a thread).

  2. What happens to setState multiple times?

    First of all, multiple setState updates are delayed, and the results cannot be immediately obtained after setState

      scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
    Copy the code

    Second, multiple setStates are saved and updated in batches at once (why batch update? Some say it’s for optimization. Through the asynchronous operation mode, cumulative update, batch merge processing, reduce the number of rendering, improve performance). The React team’s official response:

    • Maintain internal consistency. If you change to the synchronous update mode, although setState becomes synchronous, props is not.

    • Enable concurrent updates for subsequent schema upgrades. To achieve asynchronous rendering, React assigns different priorities to setState based on their data sources, such as event callback handles, animation effects, etc., and processes them concurrently based on their priorities to improve rendering performance.

  3. Why can’t hooks be written in judgment conditions

    First, let’s look at the data structure of the hooks. The hooks are connected internally by the next attribute, and if written in a judge, loop, or nested, the values of the array will be misplaced.

    • On the first rendering, plug state, DEPS, etc. into the memoizedState array in order of useState, useEffect, etc.
    • When updating, take the last recorded values out of memoizedState in that order.

    Reference link pl

  4. What is the relationship between Jsx and Fiber?

    Jsx itself is a syntactic sugar that can be understood as JS + HTML. Fiber is a data structure in React. Jsx needs to be converted by @babel/plugin-transfrom-react-jsx. Fiber is created from the ReactElement object.

    / * * *@desc Generate Fiber node */ from JSX ReactElement
    function createFiberFromElement(element, mode, lanes) {...var type = element.type;
      var key = element.key;
      var pendingProps = element.props;
      varfiber = createFiberFromTypeAndProps(type, key, pendingProps, owner, mode, lanes); .return fiber;
    }
    Copy the code
  5. ComponentWillMount, componentWillMount, componentWillUpdate why UNSAFE?

    First, the three declared period functions are executed during the Render phase of React. Second, the concurrent | blocking mode, half may perform the task of render will be higher-priority task after interrupting, repeat again, so this a few statement cycle function may be repeated many times in the rendering.

  6. Why are String refs deprecated?

  7. Please briefly describe the DIff algorithm

    First, in order to reduce the complexity of the algorithm, the DIff algorithm in React has some major premises.

    • React only diff nodes at the same level. Nodes are deleted and remounted after cross-tier operations.

    • React does not reuse components as long as they are of different types.

    • React has made some optimizations to give each node a key attribute. If the key and elementType are the same, then the node is reusable by default.

      According to the number of nodes at the same level, it can be divided into single-node diff and multi-node DIff (based on newChild).

      7.1 Single-node DIFF

      In the case of single-node diff, it first checks whether the node is present in the current tree, then checks whether the key (therefore, the key must be added when rendering JSX using map), and whether the elementType is the same. If so, it will reuse the node and delete all nodes at that level in the current tree.

      function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
          while(child ! = =null) {
            // The key is the same, so when we write the map, we must, by all means, add the key(subscript is used by default)
            if (child.key === key) {
               ...
                if (child.elementType === elementType || ( // Keep this check inline so it only runs on the false path:
                 typeof elementType === 'object'&& elementType ! = =null && elementType.$$typeof === REACT_LAZY_TYPE && resolveLazy(elementType) === child.type) {
                  // If the key is the same and the type is the same, delete the remaining nodes
                  deleteRemainingChildren(returnFiber, child.sibling);
                  // Same key, same type. Reusable nodes
                  var _existing = useFiber(child, element.props);
                  return _existing;
                }
              } 
              deleteRemainingChildren(returnFiber, child);
              break;
            } else {
              deleteChild(returnFiber, child); // If the key is different, delete it directly
            }
      
            child = child.sibling; // Diff is a single node, but there may be multiple nodes in the current number}}Copy the code

      7.2 Multi-node DIFF

      The situation of multi-node DIff is complicated and will not be described in detail. Please refer to the React Technology

  8. What is Fiber and why does it improve performance?

    Fiber is used in React to represent the virtual DOM. In the future React will be able to implement asynchronous interruptible updates with Fiber data structures. The core idea of React is to use the diff algorithm to find the difference in each state change and then re-render the page. But there are some problems.

    • Not all status updates need to be performed immediately
    • The content that needs to be updated also needs to be prioritized. For example, animations and interactive response results triggered by users should be of high priority and need immediate response. Updates generated by network requests are of general priority. When we have a low priority occupied for a long timeJs threadThe user experience is affected when high-priority tasks are blocked due to computing.FiberThe concept of interruptible rendering is designed to solve similar problems
  9. React how does react distinguish the Class component from the Function component

    Since both Class and Function components are essentially functions, the isReactComponent attribute can only be added to Component components to distinguish Function components from Class components

    function Component(props, context, updater) {... }// Add attributes to distinguish function components from class components
    Component.prototype.isReactComponent = {};
    Copy the code
  10. What are the optimizations for React?

    • Suspense,LazyLazy loading reduces loading of unnecessary elements
    • Memo.pureComponent.shouldComponnetUpdateTo reduce unnecessary repeated rendering
    • useMemoTo cache time-consuming calculations
    • Avoid using inline object reference links
    • Avoid using anonymous function reference links
    • Tweak the CSS instead of forcing components to load and unload
    • useReact.FragmentTo avoid loading extraDOM

Refer to the link

React Technology revealed

React principle

React17 source code parsing

[react17 source Boot camp](