React Source Interpretation [1] API Review and Basis

Posted by mattastic on Sun, 24 Nov 2019 03:07:35 +0100

Life is the sum of all your choices.So, what do you want to do today?- Albert Camus

Four years, like a depression in the stream of life, like a drop of water in the long river of history, but these four years, I have completed the transformation from ignorance to maturity.Looking back on the past four years, I have had entrepreneurship, illness, unspeakable pain, and joy that made me laugh continuously.

That year's backpack was still on my back; that year's code was still being implemented with similar logic; a good thing always made me love it, react was one of them, from React.createClass to React.createElement to React.Component; from Mixin to class component to functional component; from flux to redux, mobx to hooks; every time, every time, every timeLove goes deeper.At this point in time, I think that as a Zen developer, I should remember my old Valentine.

This series of articles and video explanations (WeChat Public Number: u JavaScript Full Stack u) will take a closer look at the React source code.

To keep the source code consistent, please read the same version as this text and video, which can be downloaded from github at: https://github.com/Walker-Leee/react-learn-code-v16.12.0

Interpretation is arranged as follows

Okay, that's all. Let's unveil React together!

React Foundation and API

Early react developers should all know that react and react-dom were originally in the same package, and later split react and react-dom for platform portability. I believe that those who have done react-native know that react is also used when we write react-native projects, only the components and API of react-native are used in the performance layer.So looking at the react source, let's first analyze react's definition of api.

I'll show you 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';

Component and PureComponent

The difference between the two is that PureComponent gives an additional identity that is processed in ReactFiberClassComponent to determine whether to shalloEqual.

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

Compare the values of state and props to determine if updates are required.

Another place to change the contrast is shouldComponentUpdate.

createRef

Updated ref usage, we can see that React is about to discard <div>123</div> and can only use ref in the following two ways in the future.

class App extends React.Component{

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

  render() {
    return <div ref={this.ref} />
    // Or
    return <div ref={(node) => this.ref = node} />
  }

}

forwardRef

To solve the problem of ref delivery in component encapsulation, you should know from the antd source that many components use forwardRef.For example, in a form component, @Form.create() binds the props associated with the form component to the component, this.props.validate

ReactChildren

The file contains APIs for Each, map, count, toArray, only, which are all methods for reactChildren processing.

createElement and cloneElement

We seem to be using the react createElement method less often because we now use jsx in most of our projects, mostly babel to help us convert jsx to createElement, React.createElement ('h1', {id:'title'},'Hello world').

cloneElement, as its name implies, copies an existing element.

memo

Similar to pureComponent usage in function components, shallowly compare props of function components to determine if updates are required.

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,
  };
}

ReactElement

In react, the createElement method is called and the return value is ReactElement.

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

// Predefine the type value of the ReactElement and return the ReactElement as compared to the createElement
export function createFactory(type) {
  const factory = createElement.bind(null, type);
  factory.type = type;
  return factory;
}

Let's look again 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 indicates the React node type
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    // Identify what type of change ReactElement belongs to
    type: type,
    key: key,
    ref: ref,
    props: props,

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

  return element;
};

We can see that ReactElement is just an object used to record information about nodes and perform different types of logic in subsequent operations through these attribute values in the object.At the same time, this information provides the ability to be off-platform when rendered on different platforms.

Fiber,FiberRoot

FiberRoot

type BaseFiberRootProperties = {|
  // Mount the node, the second parameter 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 react-dom
  pendingChildren: any,
  // The corresponding Fiber, Root Fiber, is currently applied
  current: Fiber,

  // The following order represents priority
  // 1) Tasks not yet committed
  // 2) Pending Tasks Not Submitted
  // 3) Possible pending tasks not submitted
  // Suspend oldest and latest tasks on submission
  earliestSuspendedTime: ExpirationTime,
  latestSuspendedTime: ExpirationTime,
  // The earliest and latest priority levels that are not known to be suspended.
  // Not sure if the oldest and latest tasks will be suspended (all task initializations are 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 there are errors thrown and there are no more updates at this time, we will try to synchronize the rendering from scratch before processing the errors
  // This value is set to `true'when renderRoot has an unhandled error
  didError: boolean,

  // The `expirationTime'property of the task waiting to be submitted
  pendingCommitExpirationTime: ExpirationTime,
  // The FiberRoot object for the completed task, if you only have one Root, it will always be the Fiber or null for that Root
  // In the commit phase, only tasks corresponding to this value will be processed
  finishedWork: Fiber | null,
  // When a task is suspended, the return content set by setTimeout is used to clean up timeout that has not yet been triggered the next time a new task is suspended
  timeoutHandle: TimeoutHandle | NoTimeout,
  // Top level context object, used only when renderSubtreeIntoContainer is actively called
  context: Object | null,
  pendingContext: Object | null,
  // Used to determine if merging is required during the first rendering
  hydrate: boolean,
  // The remaining expiration time on the current root object
  nextExpirationTimeToWorkOn: ExpirationTime,
  // Expiration time for 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 indicates whether a commit should be deferred and includes callbacks to complete the processing
  firstBatch: Batch | null,
  // Link list structure associated between root s
  nextScheduledRoot: FiberRoot | null,
|};

Fiber

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

  // key inside ReactElement
  key: null | string,

  // ReactElement.type, we call the first parameter of `createElement`
  elementType: any,

  // The resolved function/class/ associated with this fiber.
  // The content returned after the asynchronous component resolved, typically `function` or `class`, represents a function or class
  type: any,

  // The local state associated with this fiber.
  // Local state related to the current Fiber (if DOM node in browser environment)
  stateNode: any,

  // Point to his `parent'in the Fiber node tree to return up after processing this node
  return: Fiber | null,

  // Point to its first child node
  // Single Chain Table Tree Structure
  child: Fiber | null,
  // Brothers Pointing to themselves
  // The return of a sibling node points to the same parent node
  sibling: Fiber | null,
  index: number,

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

  // props brought by new updates
  pendingProps: any, 
  // props after last rendering
  memoizedProps: any,

  // Queue holding the Update generated by the Fiber's corresponding component
  updateQueue: UpdateQueue<any> | null,

  // state at last rendering
  memoizedState: any,

  // List of context s on which this Fiber depends
  firstContextDependency: ContextDependency<mixed> | null,

  // `Bitfield'used to describe the current Fiber and its subtrees
  // Coexistence mode indicates whether this subtree is rendered asynchronously by default
  // When a Fiber is created, it inherits its parent Fiber
  // Other identities can also be set when they are created
  // But it should not be modified after creation, especially before its sub-Fiber is created
  mode: TypeOfMode,

  // Effect
  // Used to record Side Effect
  effectTag: SideEffectTag,

  // A single-chain list is used to quickly find the next side effect
  nextEffect: Fiber | null,

  // First side effect in subtree
  firstEffect: Fiber | null,
  // Last side effect in subtree
  lastEffect: Fiber | null,

  // Represents at what point in the future the task should be completed
  // Does not include tasks generated by his subtree
  expirationTime: ExpirationTime,

  // Quickly determine if there are unattended changes in the subtree
  childExpirationTime: ExpirationTime,

  // During the Fiber tree update process, each Fiber will have a corresponding Fiber, current <=> workInProgress
  //Save fiber after rendering is complete
  alternate: Fiber | null,

  // Debug correlation, collect each Fiber and subtree rendering time

  actualDuration?: number,
  actualStartTime?: number,
  selfBaseDuration?: number,
  treeBaseDuration?: number,
  _debugID?: number,
  _debugSource?: Source | null,
  _debugOwner?: Fiber | null,
  _debugIsCurrentlyTiming?: boolean,
|};

effectTags,ReactWorkTag,sideEffects

These three files mainly define the operation-related types in react. It is worth mentioning that the definition and combination of types in react are very clever. If students have not used this idea before, you can try this method in the privilege 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;

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;

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;

Update,UpdateQueue

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

  // The tag identifies the update type
  // UpdateState -> 0;
  // ReplaceState -> 1;
  // ForceUpdate -> 2;
  // CaptureUpdate -> 3;
  tag: 0 | 1 | 2 | 3,
  // Update content, such as the first parameter received when calling setState
  payload: any,
  // Corresponding callback function when calling setState or render
  callback: (() => mixed) | null,

  // Point to Next Update
  next: Update<State> | null,
  // Point to the next side effect
  nextEffect: Update<State> | null,
};

export type UpdateQueue<State> = {
  // Updated state after each operation
  baseState: State,

  // Update of the Team Leader
  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,
};

React.Children

There is a structure in the data structure - the chain table. I don't know if I can remember the traversal of the chain table?The most common traversal of a linked list is through recursion, an api implementation that relies on recursion.Let's look at the code 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);
}
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);
}
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);
  }
}

Topics: Javascript React snapshot github Attribute