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