preface
As a front-end page and demand shredder, it can achieve limited improvement by repeating the same business requirements in daily work. In order to jump out of this mountain and open up a new vision, I suggest you read the source code of the top open source library on the market. This is a great opportunity to learn and master js language features (since the front-end has developed to the present, large-scale applications are highly dependent on the framework. Under normal circumstances, ordinary developers have no chance to touch the underlying language features), and it is also an opportunity to deeply understand the underlying thinking of the framework. Here I choose react as the first one. Many articles on the source code analysis of react are either too old, or only part of the code or pseudo code is intercepted. Here I will choose react version 16.8.6 as an example, starting from line 0, without missing any source details, and share my experience in the source code reading process with you. I hope to make progress together with you. The source code involved in this series of blog posts will be put in git warehouse and linked at the end of the article.
text
In this article, we will focus on the core of react virtual DOM implementation, the definition of component.
- component and PureComponent definitions
/** * Base class helpers for the updating state of a component. */ // Constructor for component function Component(props, context, updater) { this.props = props; this.context = context; // If the component uses refs of the string, we will specify a different object // If a component has string refs, we will assign a different object later. this.refs = emptyObject; // Initialize the default Updater. The real Updater will be injected into the renderer // We initialize the default updater but the real one gets injected by the // renderer. this.updater = updater || ReactNoopUpdateQueue; } // Define prototype properties Component.prototype.isReactComponent = {}; // To set a subset of States, always use this to change the States. You need to this.sate As immutable under normal condition // There is no guarantee this.state It will be updated immediately. You may return the old value after setting the value // It is not guaranteed that setState calls are synchronized because multiple calls will eventually be merged, and you can provide an optional callback, which is called after setStates is actually finished. // When a function is passed to setState, it will be called at a certain point in the future, and the latest parameters (state,props,context, etc.) may be used this.xxx dissimilarity // Because your function is called after receiveProps, before shouldComponentUpdate. In this new stage, props and context are not assigned to this. /** * Sets a subset of the state. Always use this to mutate * state. You should treat `this.state` as immutable. * * There is no guarantee that `this.state` will be immediately updated, so * accessing `this.state` after calling this method may return the old value. * * There is no guarantee that calls to `setState` will run synchronously, * as they may eventually be batched together. You can provide an optional * callback that will be executed when the call to setState is actually * completed. * * When a function is provided to setState, it will be called at some point in * the future (not synchronously). It will be called with the up to date * component arguments (state, props, context). These values can be different * from this.* because your function may be called after receiveProps but before * shouldComponentUpdate, and this new state, props, and context will not yet be * assigned to this. * * @param {object|function} partialState Next partial state or function to * produce next partial state to be merged with current state. * @param {?function} callback Called after state is updated. * @final * @protected */ Component.prototype.setState = function (partialState, callback) { // void 0 === undefined saves bytes and prevents undefined from being injected // partialState needs to be an object, function or null !(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null) ? invariant(false, 'setState(...): takes an object of state variables to update or a function which returns an object of state variables.') : void 0; // Enter queue status update this.updater.enqueueSetState(this, partialState, callback, 'setState'); }; // Forced refresh, which can only be called when the non DOM transformation state is in progress // When some deep state changes but setState is not called, the method will not call shouldComponentUpdate, but componentWillUpdate and componentDidUpdate will be called /** * Forces an update. This should only be invoked when it is known with * certainty that we are **not** in a DOM transaction. * * You may want to call this when you know that some deeper aspect of the * component's state has changed but `setState` was not called. * * This will not invoke `shouldComponentUpdate`, but it will invoke * `componentWillUpdate` and `componentDidUpdate`. * * @param {?function} callback Called after update is complete. * @final * @protected */ Component.prototype.forceUpdate = function (callback) { this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); }; // Abandoned APIs. These APIs only exist on classic class components. We will abandon them. Instead of removing them directly, we define getter s and throw them in the wrong place /** * Deprecated APIs. These APIs used to exist on classic React classes but since * we would like to deprecate them, we're not going to move them over to this * modern base class. Instead, we define a getter that warns if it's accessed. */ { var deprecatedAPIs = { isMounted: ['isMounted', 'Instead, make sure to clean up subscriptions and pending requests in ' + 'componentWillUnmount to prevent memory leaks.'], replaceState: ['replaceState', 'Refactor your code to use setState instead (see ' + 'https://github.com/facebook/react/issues/3236).'] }; // Define alarm function of abandoned api var defineDeprecationWarning = function (methodName, info) { Object.defineProperty(Component.prototype, methodName, { get: function () { lowPriorityWarning$1(false, '%s(...) is deprecated in plain JavaScript React classes. %s', info[0], info[1]); return undefined; } }); }; // Inject getter in turn for (var fnName in deprecatedAPIs) { if (deprecatedAPIs.hasOwnProperty(fnName)) { defineDeprecationWarning(fnName, deprecatedAPIs[fnName]); } } } // A prototype is a prototype of a real component function ComponentDummy() {} ComponentDummy.prototype = Component.prototype; /** * Convenience component with default shallow equality check for sCU. */ // A convenient component, the default shallow equality check, is actually a constructor function PureComponent(props, context, updater) { this.props = props; this.context = context; // If there is a ref of string type, we will assign a different object later // If a component has string refs, we will assign a different object later. this.refs = emptyObject; this.updater = updater || ReactNoopUpdateQueue; } // For prototype and constructor, please refer to https://blog.csdn.net/cc18868876837/article/details/81211729 // Pure component prototype var pureComponentPrototype = PureComponent.prototype = new ComponentDummy(); // Defining a constructor for a pure component prototype pureComponentPrototype.constructor = PureComponent; // Avoid an extra prototype jump for these methods. // Inject Component's prototype on the prototype again _assign(pureComponentPrototype, Component.prototype); // Flag bit, judgment is pure component pureComponentPrototype.isPureReactComponent = true;
The constructor Component of Component is defined here, and then a series of IDE prototype methods are defined, such as setState, forceUpdate, etc. then, for some abandoned APIs, get methods are added to the prototype of Component, so as to throw errors when users call. Next, we define a transition Component, ComponentDummy, whose prototype is the prototype of Component, in fact, the prototype of PureComponent
- createRet and component call stack
// an immutable object with a single mutable value // An immutable object, an immutable value // The closed object is still the same as the object itself // Through Object.isSealed To determine whether the current object is closed // You cannot add any unknown properties to a closed object or add accessors to its known properties // Known properties can be modified // https://www.jianshu.com/p/96220f921272 function createRef() { // Only current attribute var refObject = { current: null }; { Object.seal(refObject); } return refObject; } /** * Keeps track of the current dispatcher. */ // Track current distributors var ReactCurrentDispatcher = { /** * @internal * @type {ReactComponent} */ current: null }; /** * Keeps track of the current owner. * * The current owner is the component who should own any components that are * currently being constructed. */ // Trace the current owner of react, which refers to the parent component of all the components being constructed var ReactCurrentOwner = { /** * @internal * @type {ReactComponent} */ current: null }; // Regular, match any content and add positive and negative diagonal bars // The bracketed content is grouping https://www.jianshu.com/p/f09508c14e65 // Match if it is a global match, it returns all matching items. If it is not a matching string, location, original input. If there is a group, the second item is a matching group var BEFORE_SLASH_RE = /^(.*)[\\\/]/; // Describe the reference location of the component var describeComponentFrame = function (name, source, ownerName) { var sourceInfo = ''; if (source) { var path = source.fileName; // Resolve file name var fileName = path.replace(BEFORE_SLASH_RE, ''); { // In DEV, include code for a common special case: // prefer "folder/index.js" instead of just "index.js". // In the development environment, if the filename is index, output the filename with the upper path if (/^index\./.test(fileName)) { // Resolves the file name before the backslash var match = path.match(BEFORE_SLASH_RE); if (match) { var pathBeforeSlash = match[1]; if (pathBeforeSlash) { // Get the name of the folder before the file name var folderName = pathBeforeSlash.replace(BEFORE_SLASH_RE, ''); fileName = folderName + '/' + fileName; } } } } // Get the latest folder name and file name, spell the number of lines of code sourceInfo = ' (at ' + fileName + ':' + source.lineNumber + ')'; } else if (ownerName) { sourceInfo = ' (created by ' + ownerName + ')'; } return '\n in ' + (name || 'Unknown') + sourceInfo; }; var Resolved = 1; // Refine and analyze inert components function refineResolvedLazyComponent(lazyComponent) { // If it has been resolved, return the result return lazyComponent._status === Resolved ? lazyComponent._result : null; } // Get the name of the outer component function getWrappedName(outerType, innerType, wrapperName) { var functionName = innerType.displayName || innerType.name || ''; // The displayName of outerType takes precedence, otherwise the combination of wrapperName and functionName return outerType.displayName || (functionName !== '' ? wrapperName + '(' + functionName + ')' : wrapperName); }
Here, we first define the implementation of createRef, and then define react current dispatcher. The hooks series api added after react 16.8.3 is similar to the dispatcher in redux. The react current dispatcher here will also be used in the subsequent definitions of hooks api. The next step is
The core of the describeComponentFrame method is to get the source of the file source (mainly the file name and the number of lines of code). Next, the method getWrappedName to get the component name of the component's parent component is defined
- Get component name and current component call stack details in react debug mode
// Get component name function getComponentName(type) { if (type == null) { // Host root, text node or just invalid type. // If it is root, text node or non-existent type, return null return null; } { // If the tag of type is a number if (typeof type.tag === 'number') { // Warning: unexpected object received. This may be a bug in react. Please refer to issue warningWithoutStack$1(false, 'Received an unexpected object in getComponentName(). ' + 'This is likely a bug in React. Please file an issue.'); } } // If it's a constructor, look at its static property displayName or name if (typeof type === 'function') { return type.displayName || type.name || null; } // If the string is returned directly if (typeof type === 'string') { return type; } switch (type) { // If it is the current node of react, these are all defined by the original symbol case REACT_CONCURRENT_MODE_TYPE: return 'ConcurrentMode'; case REACT_FRAGMENT_TYPE: return 'Fragment'; // If it's an entrance case REACT_PORTAL_TYPE: return 'Portal'; // If it's an analyzer case REACT_PROFILER_TYPE: return 'Profiler'; case REACT_STRICT_MODE_TYPE: return 'StrictMode'; case REACT_SUSPENSE_TYPE: return 'Suspense'; } // If type is an object if (typeof type === 'object') { // Judge by $$typeof switch (type.$$typeof) { case REACT_CONTEXT_TYPE: return 'Context.Consumer'; case REACT_PROVIDER_TYPE: return 'Context.Provider'; // If forward ref case REACT_FORWARD_REF_TYPE: return getWrappedName(type, type.render, 'ForwardRef'); // If it is a memo type, call itself recursively case REACT_MEMO_TYPE: return getComponentName(type.type); // If it's a lazy type case REACT_LAZY_TYPE: { var thenable = type; // Refine and analyze inert components var resolvedThenable = refineResolvedLazyComponent(thenable); if (resolvedThenable) { return getComponentName(resolvedThenable); } } } } // Finally return null return null; } // react is debug ging the frame. It can be understood that there are some methods in an object to call the call stack of the current component var ReactDebugCurrentFrame = {}; // Elements currently being validated var currentlyValidatingElement = null; // Set the element currently being validated function setCurrentlyValidatingElement(element) { { currentlyValidatingElement = element; } } { // The implementation of the stack is injected through the current renderer // Stack implementation injected by the current renderer. ReactDebugCurrentFrame.getCurrentStack = null; // How to add enumeration // Essentially an appendix to the return call stack ReactDebugCurrentFrame.getStackAddendum = function () { var stack = ''; // Add an extra top frame while an element is being validated // Add an additional top-level framework if an element is currently being validated if (currentlyValidatingElement) { // Get the name of the element var name = getComponentName(currentlyValidatingElement.type); // Get element owner var owner = currentlyValidatingElement._owner; // Get the directory location of the source stack += describeComponentFrame(name, currentlyValidatingElement._source, owner && getComponentName(owner.type)); } // Delegate to the injected renderer-specific implementation // Pass to the special implementation in the renderer to get the stack // If getCurrentStack is replicated, append the information provided by the method var impl = ReactDebugCurrentFrame.getCurrentStack; if (impl) { stack += impl() || ''; } return stack; }; }
getComponentName gets the name of the component according to the type of the incoming react Element (Element), and judges the returned result by judging the type and the value of $$typeof on the type. $$typeof is a variable defined inside the react Element, which records the type of the Element. It will be mentioned in the following code. setCurrentlyValidatingElement will be called repeatedly in some subsequent validate methods to set the Element currently being verified so that the call stack when the subsequent output is thrown incorrectly can be set.
- Definition of internal control component, attribute reserved word of react element and hasValidXXX method
// Control components shared in react var ReactSharedInternals = { // Current distributor ReactCurrentDispatcher: ReactCurrentDispatcher, // Current owner ReactCurrentOwner: ReactCurrentOwner, // Object.assign Avoid packing twice under UMD // Used by renderers to avoid bundling object-assign twice in UMD bundles: assign: _assign }; { _assign(ReactSharedInternals, { // There should be no // These should not be included in production. ReactDebugCurrentFrame: ReactDebugCurrentFrame, // Shim for React DOM 16.0.0 which still destructured (but not used) this. // TODO: remove in React 17.0. // react tree hook ReactComponentTreeHook: {} }); } // Similar to the warning of invariance, only print when the condition is not satisfied /** * Similar to invariant but only logs a warning if the condition is not met. * This can be used to log issues in development environments in critical * paths. Removing the logging code for production environments will keep the * same logic and follow the same code paths. */ // First assign warningWithoutStack var warning = warningWithoutStack$1; // Essentially a warning method with call stack { // The second condition is formatting warning = function (condition, format) { if (condition) { return; } // Get the current call sequence var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame; var stack = ReactDebugCurrentFrame.getStackAddendum(); // eslint-disable-next-line react-internal/warning-and-invariant-args // Parameters after assembly for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { args[_key - 2] = arguments[_key]; } // Reuse the original warning method. The previous content remains the same, and the later content collapses the call sequence information warningWithoutStack$1.apply(undefined, [false, format + '%s'].concat(args, [stack])); }; } var warning$1 = warning; // Define the hasOwnProperty method var hasOwnProperty = Object.prototype.hasOwnProperty; // Property reserved word var RESERVED_PROPS = { key: true, ref: true, __self: true, __source: true }; // Special attribute key display alarm // void 0 is undefined var specialPropKeyWarningShown = void 0; var specialPropRefWarningShown = void 0; // Exclude the situation that ref is warning, and judge whether ref exists function hasValidRef(config) { { // If config has ref attribute if (hasOwnProperty.call(config, 'ref')) { // Get get method var getter = Object.getOwnPropertyDescriptor(config, 'ref').get; // If the getter is warning, return false if (getter && getter.isReactWarning) { return false; } } } // Otherwise, judge according to whether it is undefined return config.ref !== undefined; } // Whether there are available properties // Logic is very similar to ref function hasValidKey(config) { { if (hasOwnProperty.call(config, 'key')) { var getter = Object.getOwnPropertyDescriptor(config, 'key').get; if (getter && getter.isReactWarning) { return false; } } } return config.key !== undefined; } // Defining the warning Getter of key attribute function defineKeyPropWarningGetter(props, displayName) { // Alarm about accessing key var warnAboutAccessingKey = function () { if (!specialPropKeyWarningShown) { specialPropKeyWarningShown = true; // The key cannot be obtained as a parameter. It is expropriated by react // key can't be used as a parameter. Trying to get it will only return undefined. If you want to get the value on the subcomponent, you should pass the attribute of another name warningWithoutStack$1(false, '%s: `key` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName); } }; // This function is defined as react warn warnAboutAccessingKey.isReactWarning = true; // Define getter for the key attribute of the input parameter to avoid external access Object.defineProperty(props, 'key', { get: warnAboutAccessingKey, configurable: true }); } // This part is very similar to key's // Define how to get ref function defineRefPropWarningGetter(props, displayName) { var warnAboutAccessingRef = function () { if (!specialPropRefWarningShown) { specialPropRefWarningShown = true; warningWithoutStack$1(false, '%s: `ref` is not a prop. Trying to access it will result ' + 'in `undefined` being returned. If you need to access the same ' + 'value within the child component, you should pass it as a different ' + 'prop. (https://fb.me/react-special-props)', displayName); } }; warnAboutAccessingRef.isReactWarning = true; Object.defineProperty(props, 'ref', { get: warnAboutAccessingRef, configurable: true }); }
Next, react defines an object, ReactSharedInternals, which contains some methods to obtain the global public, such as ReactCurrentDispatcher (hooks related functions), and then defines the warning method with stack information, which is actually the result of getStackAddendum assembling warningWithoutStack . Next, the reserved words of the react Element attribute are defined through a constant: key, Ref__ Self and__ source. Next, we define a method to verify whether a key or ref is available.
To be continued
ending
For the sake of space, the source code analysis of this article is over, and the links will be synchronized here when the next article is published. If there are any mistakes or omissions, please discuss in the comment area. If there are any defects in the translation of official notes, please point out~
Warehouse address:
React 16.8.3 source code annotation warehouse
Next:
be under construction