Life is the sum of all your choices. So, what are you doing today? — Albert Camus

Four years, like a pool of water in the stream of life, like a drop of water in the long river of history, but it is these four years, I have completed the transformation from ignorant to mature. Looking back over the past four years, I have started a business, been ill, had unspeakable pain, also have let me laugh uninterrupted joy.

That year’s backpack, still carrying; The code of that year is still implemented with similar logic; React. CreateClass. CreateElement. React.ponent. From mixins to class Component to functional Component; From Flux to Redux, mobx to hooks; Each time you take a step closer, each time you love a little deeper. At this point, I think it’s time for me, as a Zen developer, to honor my old lover.

This series of articles and videos will take a closer look at the React source code.

To ensure consistent source code, please read the same version of this article and video. You can download it from github at github.com/Walker-Leee…

The interpretation arrangement is as follows

Let’s uncover the mystery of React!

React infrastructure and APIS

Those of you who did react development in the early days know that react and React – DOM were in the same package at first. Later, react and React – DOM were separated for platform portability. Those of you who did React – Native know that when we wrote react- Native, React is also used, but the react-Native component and API are used in the presentation layer. React API definition react API definition

I’m showing some code snippets from React here

import {Component, PureComponent} from './ReactBaseClasses';
import {createRef} from './ReactCreateRef';
import {forEach, map, count, toArray, only} from './ReactChildren';
import {
  createElement,
  createFactory,
  cloneElement,
  isValidElement,
  jsx,
} from './ReactElement';
import {createContext} from './ReactContext';
import {lazy} from './ReactLazy';
import forwardRef from './forwardRef';
import memo from './memo';
import {
  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useDebugValue,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,
  useResponder,
  useTransition,
  useDeferredValue,
} from './ReactHooks';
Copy the code

Component and PureComponent

The difference is that the PureComponent gives an extra flag, which is handled in the ReactFiberClassComponent to determine whether to shalloEqual.

if (ctor.prototype && ctor.prototype.isPureReactComponent) {
  return(! shallowEqual(oldProps, newProps) || ! shallowEqual(oldState, newState) ); }Copy the code

Compare the values of state and props to determine if an update is required.

Another place to do this is in shouldComponentUpdate.

createRef

React will discard

123

and will only use ref in the following two ways.

class App extends React.Component{

  constructor() {
    this.ref = React.createRef()
  }

  render() {
    return<div ref={this.ref} /> // return <div ref={(node) => this.ref = node} />}}Copy the code

forwardRef

The forwardRef function is used to solve the forwardRef problem when the component is wrapped. For example, in the form component, @form.create () binds the props associated with the form component to the component, this.props. Validate

ReactChildren

This file contains apis such as forEach, Map, count, toArray, and only, which are all methods for reactChildren.

The createElement method and cloneElement

We are using the react seems rare createElement method method, because in our project now mostly use the JSX, most of the time is Babel help us converts JSX createElement method, the react. CreateElement method (‘ h1, {id: ‘title’}, “hello world”).

CloneElement, as its name implies, copies existing elements.

memo

Purecomponent-like usage of function components, shallow comparison of functions components to determine if they need to be updated.

export default function memo<Props> (type: React$ElementType, compare? : (oldProps: Props, newProps: Props) = >boolean.){
  return {
    ? typeof: REACT_MEMO_TYPE,
    type,
    compare: compare === undefined ? null : compare,
  };
}
Copy the code

ReactElement

In React, the createElement method is called and returns ReactElement.

export function createElement(type, config, children) {
  // ...
  
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

// In contrast to createElement, ReactElement type is predefined and ReactElement is returned
export function createFactory(type) {
  const factory = createElement.bind(null, type);
  factory.type = type;
  return factory;
}
Copy the code

Let’s look at the definition of ReactElement

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    // This parameter specifies the React node type
    ?typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    // What type is ReactElement
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    / / record
    _owner: owner,
  };

  return element;
};
Copy the code

You can see that ReactElement is just an object that records node information, and then performs different types of logic through these attribute values in the object in subsequent operations. At the same time, this information provides the ability to be platform-independent when rendered on different platforms.

Fiber, FiberRoot

FiberRoot

type BaseFiberRootProperties = {|
  // Mount the node, the second argument received in the reactdom.render method
  containerInfo: any,
  // This property is used for persistent updates. In other words, incremental update platforms are not supported and are not involved in the React-DOM
  pendingChildren: any,
  // The current application corresponds to Fiber, namely Root Fiber
  current: Fiber,

  // The following order indicates the priority
  // 1) Tasks not committed
  // 2) Pending tasks that have not yet been committed
  // 3) Uncommitted tasks that may be pending
  // The oldest and most recent tasks are suspended at commit time
  earliestSuspendedTime: ExpirationTime,
  latestSuspendedTime: ExpirationTime,
  // The earliest and latest priority levels that are not known to be suspended.
  // The oldest and latest tasks that are not sure whether they will be suspended (all tasks are initialized in this state)
  earliestPendingTime: ExpirationTime,
  latestPendingTime: ExpirationTime,
  // The latest priority level that was pinged by a resolved promise and can be retried.
  latestPingedTime: ExpirationTime,

  // If an error is thrown and there are no more updates at this point, we will try to synchronize the rendering from scratch before handling the error
  // This value is set to true when renderRoot fails to handle an error
  didError: boolean,

  // Wait for the 'expirationTime' property of a submitted task
  pendingCommitExpirationTime: ExpirationTime,
  // FiberRoot object for completed tasks, if you have only one Root, it can only be Fiber for that Root, or null
  // During the COMMIT phase, only the tasks corresponding to this value are processed
  finishedWork: Fiber | null.// When a task is suspended, the setTimeout set returns the content, which is used to clean up the timeout that has not been triggered the next time a new task is suspended
  timeoutHandle: TimeoutHandle | NoTimeout,
  / / the top-level context object, only take the initiative to call renderSubtreeIntoContainer will only be used to
  context: Object | null.pendingContext: Object | null.// Used to determine whether a merge is needed on the first rendering
  hydrate: boolean,
  // The remaining expiration time on the current root object
  nextExpirationTimeToWorkOn: ExpirationTime,
  // The expiration time of the current update
  expirationTime: ExpirationTime,
  // List of top-level batches. This list indicates whether a commit should be
  // deferred. Also contains completion callbacks.
  // Top-level batch task, which specifies whether a COMMIT should be deferred and includes a callback after completion
  firstBatch: Batch | null.// The linked list structure associated between root
  nextScheduledRoot: FiberRoot | null|};Copy the code

Fiber

// Fiber corresponds to a component that needs to be processed or has been processed. The component and Fiber can be one-to-many
type Fiber = {|
  // Different component types
  tag: WorkTag,

  // ReactElement key
  key: null | string,

  // ReactElement. Type, the first argument to createElement
  elementType: any,

  // The resolved function/class/ associated with this fiber.
  'function' or 'class' will return when the asynchronous component is resolved
  type: any,

  // The local state associated with this fiber.
  // Local state relative to current Fiber (DOM node in browser context)
  stateNode: any,

  // Point to 'parent' in the Fiber tree to return up after processing the node
  return: Fiber | null.// points to its first child node
  // Single list tree structure
  child: Fiber | null.// points to its sibling node
  // The sibling node's return points to the same parent node
  sibling: Fiber | null.index: number,

  / / ref attribute
  ref: null | (((handle: mixed) = > void) & {_stringRef: ?string}) | RefObject,

  // The new update comes with props
  pendingProps: any, 
  // Props after the last rendering
  memoizedProps: any,

  // Queue to store the Update generated by the corresponding component of the Fiber
  updateQueue: UpdateQueue<any> | null.// State from the last rendering
  memoizedState: any,

  // list containing the fiber-dependent context
  firstContextDependency: ContextDependency<mixed> | null.// Describe the current 'Bitfield' of Fiber and other subtrees
  // The coexistence mode indicates whether the subtree is rendered asynchronously by default
  // When Fiber is created, it inherits its parent Fiber
  // Other flags can also be set at creation time
  // However, it should not be modified after creation, especially before its sub-fiber is created
  mode: TypeOfMode,

  // Effect
  // To record Side Effect
  effectTag: SideEffectTag,

  // Single linked lists are used to quickly find the next side effect
  nextEffect: Fiber | null.// The first side effect in the subtree
  firstEffect: Fiber | null.// Last side effect in the subtree
  lastEffect: Fiber | null.// represents the point in the future at which the task should be completed
  // Does not include tasks generated by his subtree
  expirationTime: ExpirationTime,

  // Quickly determine if there are any changes in the subtree that are not waiting
  childExpirationTime: ExpirationTime,

  // During Fiber tree update, each Fiber has a corresponding Fiber, current <==> workInProgress
  // After rendering, save fiber
  alternate: Fiber | null.// Debug related, collect each Fiber and subtree rendering timeactualDuration? : number, actualStartTime? : number, selfBaseDuration? : number, treeBaseDuration? : number, _debugID? : number, _debugSource? : Source |null, _debugOwner? : Fiber |null, _debugIsCurrentlyTiming? : boolean, |};Copy the code

EffectTags, ReactWorkTag, sideEffects

These three files mainly define the types related to react operations. It is worth noting that the types in React are cleverly defined and combined. If students have not used this idea before, they can try this method in the permission design system.

effectTags

/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */

export type SideEffectTag = number;

// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = / * * / 0b00000000000;
export const PerformedWork = / * * / 0b00000000001;

// You can change the rest (and add more).
export const Placement = / * * / 0b00000000010;
export const Update = / * * / 0b00000000100;
export const PlacementAndUpdate = / * * / 0b00000000110;
export const Deletion = / * * / 0b00000001000;
export const ContentReset = / * * / 0b00000010000;
export const Callback = / * * / 0b00000100000;
export const DidCapture = / * * / 0b00001000000;
export const Ref = / * * / 0b00010000000;
export const Snapshot = / * * / 0b00100000000;

// Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /*   */ 0b00110100100;

// Union of all host effects
export const HostEffectMask = / * * / 0b00111111111;

export const Incomplete = / * * / 0b01000000000;
export const ShouldCapture = / * * / 0b10000000000;
Copy the code

ReactWorkTag

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
Copy the code

sideEffects

/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow */

export type SideEffectTag = number;

// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = / * * / 0b00000000000;
export const PerformedWork = / * * / 0b00000000001;

// You can change the rest (and add more).
export const Placement = / * * / 0b00000000010;
export const Update = / * * / 0b00000000100;
export const PlacementAndUpdate = / * * / 0b00000000110;
export const Deletion = / * * / 0b00000001000;
export const ContentReset = / * * / 0b00000010000;
export const Callback = / * * / 0b00000100000;
export const DidCapture = / * * / 0b00001000000;
export const Ref = / * * / 0b00010000000;
export const Snapshot = / * * / 0b00100000000;

// Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /*   */ 0b00110100100;

// Union of all host effects
export const HostEffectMask = / * * / 0b00111111111;

export const Incomplete = / * * / 0b01000000000;
export const ShouldCapture = / * * / 0b10000000000;
Copy the code

The Update, UpdateQueue

export type Update<State> = {
  // Update expiration time
  expirationTime: ExpirationTime,

  // This tag identifies the update type
  // UpdateState -> 0;
  // ReplaceState -> 1;
  // ForceUpdate -> 2;
  // CaptureUpdate -> 3;
  tag: 0 | 1 | 2 | 3.// Update the content, such as the first argument received when calling setState
  payload: any,
  // The corresponding callback function when setState or render is called
  callback: (() = > mixed) | null.// Points to the next update
  next: Update<State> | null.// Point to next side effect
  nextEffect: Update<State> | null};export type UpdateQueue<State> = {
  // Update state after each operation
  baseState: State,

  // Team leader Update
  firstUpdate: Update<State> | null.// Update at the end of the queue
  lastUpdate: Update<State> | null.firstCapturedUpdate: Update<State> | null.lastCapturedUpdate: Update<State> | null.firstEffect: Update<State> | null.lastEffect: Update<State> | null.firstCapturedEffect: Update<State> | null.lastCapturedEffect: Update<State> | null};Copy the code

React.Children

There is a structure in the data structure, linked list, do you remember the traversal of the linked list? The API implementation relies on recursion for the most common traversal of linked lists. Let’s look at the snippet implementation using forEach as an example.

function forEachChildren(children, forEachFunc, forEachContext) {
  if (children == null) {
    return children;
  }
  const traverseContext = getPooledTraverseContext(
    null.null,
    forEachFunc,
    forEachContext,
  );
  traverseAllChildren(children, forEachSingleChild, traverseContext);
  releaseTraverseContext(traverseContext);
}
Copy the code
function traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext,) {
  const type = typeof children;

  if (type === 'undefined' || type === 'boolean') {
    // All of the above are perceived as null.
    children = null;
  }

  let invokeCallback = false;

  if (children === null) {
    invokeCallback = true;
  } else {
    switch (type) {
      case 'string':
      case 'number':
        invokeCallback = true;
        break;
      case 'object':
        switch (children.?typeof) {
          case REACT_ELEMENT_TYPE:
          case REACT_PORTAL_TYPE:
            invokeCallback = true; }}}if (invokeCallback) {
    callback(
      traverseContext,
      children,
      // If it's the only child, treat the name as if it was wrapped in an array
      // so that it's consistent if the number of children grows.
      nameSoFar === ' ' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
    );
    return 1;
  }

  let child;
  let nextName;
  let subtreeCount = 0; // Count of children found in the current subtree.
  const nextNamePrefix =
    nameSoFar === ' ' ? SEPARATOR : nameSoFar + SUBSEPARATOR;

  if (Array.isArray(children)) {
    for (let i = 0; i < children.length; i++) { child = children[i]; nextName = nextNamePrefix + getComponentKey(child, i); subtreeCount += traverseAllChildrenImpl( child, nextName, callback, traverseContext, ); }}else {
    const iteratorFn = getIteratorFn(children);
    if (typeof iteratorFn === 'function') {

      const iterator = iteratorFn.call(children);
      let step;
      let ii = 0;
      while (!(step = iterator.next()).done) {
        child = step.value;
        nextName = nextNamePrefix + getComponentKey(child, ii++);
        subtreeCount += traverseAllChildrenImpl(
          child,
          nextName,
          callback,
          traverseContext,
        );
      }
    } else if (type === 'object') {
      let addendum = ' ';
      const childrenString = ' ' + children;
      invariant(
        false.'Objects are not valid as a React child (found: %s).%s',
        childrenString === '[object Object]'
          ? 'object with keys {' + Object.keys(children).join(', ') + '} ': childrenString, addendum, ); }}return subtreeCount;
}

function traverseAllChildren(children, callback, traverseContext) {
  if (children == null) {
    return 0;
  }

  return traverseAllChildrenImpl(children, ' ', callback, traverseContext);
}
Copy the code
const POOL_SIZE = 10;
const traverseContextPool = [];
function releaseTraverseContext(traverseContext) {
  traverseContext.result = null;
  traverseContext.keyPrefix = null;
  traverseContext.func = null;
  traverseContext.context = null;
  traverseContext.count = 0;
  if(traverseContextPool.length < POOL_SIZE) { traverseContextPool.push(traverseContext); }}Copy the code