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
The files of react library are mainly divided into two parts: react-dom.development.js and react.development.js (the source file is selected here, not the production environment min.js First, let's look at the introduction of react DOM on npm:
This package serves as the entry point to the DOM and server renderers for React. It is intended to be paired with the generic React package, which is shipped as react to npm
The function of this package is developed for react as the entry of DOM element and server-side renderer. It needs to be used together with react. It is bundled with react and distributed through npm together. In short, react-dom.js It mainly refers to the parts related to the real DOM in react (the renderers of the front and back end rendering, etc.), react.js It is the core source of react, including the creation of react elements (virtual DOM), cloning and other logic. Give us a shot first, react-dom.development.js Source code: 21429 lines, react.development.js Source code about 2000 lines, persimmon pick up soft pinch, we from react.development.js At first, gossip less, directly into the source part.
code analysis
- type definition
/** @license React v16.8.6 * react.development.js * * 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. */ 'use strict'; if (process.env.NODE_ENV !== "production") { (function() { 'use strict'; var _assign = require('object-assign'); var checkPropTypes = require('prop-types/checkPropTypes'); // TODO: this is special because it gets imported during build. var ReactVersion = '16.8.6'; // Use Symbol to mark react like attributes. If there is no native Symbol or putty syntax, use a number to mark // The Symbol used to tag the ReactElement-like types. If there is no native Symbol // nor polyfill, then a plain number is used for performance. var hasSymbol = typeof Symbol === 'function' && Symbol.for; // Symbol.for Use the string as the key to create a symbol. If there is one, use the previous one. Otherwise, create a new one // These are the internal APIs of react. You can check the unclear ones on the official website var REACT_ELEMENT_TYPE = hasSymbol ? Symbol.for('react.element') : 0xeac7; var REACT_PORTAL_TYPE = hasSymbol ? Symbol.for('react.portal') : 0xeaca; var REACT_FRAGMENT_TYPE = hasSymbol ? Symbol.for('react.fragment') : 0xeacb; var REACT_STRICT_MODE_TYPE = hasSymbol ? Symbol.for('react.strict_mode') : 0xeacc; var REACT_PROFILER_TYPE = hasSymbol ? Symbol.for('react.profiler') : 0xead2; var REACT_PROVIDER_TYPE = hasSymbol ? Symbol.for('react.provider') : 0xeacd; var REACT_CONTEXT_TYPE = hasSymbol ? Symbol.for('react.context') : 0xeace; var REACT_CONCURRENT_MODE_TYPE = hasSymbol ? Symbol.for('react.concurrent_mode') : 0xeacf; var REACT_FORWARD_REF_TYPE = hasSymbol ? Symbol.for('react.forward_ref') : 0xead0; var REACT_SUSPENSE_TYPE = hasSymbol ? Symbol.for('react.suspense') : 0xead1; var REACT_MEMO_TYPE = hasSymbol ? Symbol.for('react.memo') : 0xead3; // Lazy load flag var REACT_LAZY_TYPE = hasSymbol ? Symbol.for('react.lazy') : 0xead4; // Define symbol. If it is not supported, it will use the bottom-up scheme // Iterator returning symbol var MAYBE_ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; // key of attribute // To be an iteratable object, an object must implement the@ @ iterator method, which means that the object must have a key of the@ @ iterator property, which can be passed through the constant Symbol.iterator Access this property // https://www.cnblogs.com/pengsn/p/12892954.html var FAUX_ITERATOR_SYMBOL = '@@iterator';
Here is a brief introduction, react.js Each guide uses two public packages, object assign and prop types / checkproptypes. Object assign is mainly for compatibility with IE browser. After all, other mainstream green browsers and Node.js 4. All the above environments have realized the native Object.assignapi And prop types / checkproptypes are mainly used to implement attribute verification.
react uses Symbol to identify the built-in attributes in priority to ensure uniqueness. If it is not supported, it uses a 4-digit hexadecimal number to degrade.
- Get the iterator and define the underlying error reporting function
// Functions that return iterators function getIteratorFn(maybeIterable) { if (maybeIterable === null || typeof maybeIterable !== 'object') { return null; } // Get the iterator of the passed in parameter or@ @ iterator var maybeIterator = MAYBE_ITERATOR_SYMBOL && maybeIterable[MAYBE_ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]; if (typeof maybeIterator === 'function') { return maybeIterator; } return null; } // Using invariant to judge the invariance of input parameter // Using a format similar to sprintf to process input parameters, the relevant information will be removed, but the function itself will be retained in the production environment /** * Use invariant() to assert state which your program assumes to be true. * * Provide sprintf-style format (only %s is supported) and arguments * to provide information about what broke and what you were * expecting. * * The invariant message will be stripped in production, but the invariant * will remain to ensure logic does not differ in production. */ // Validation format var validateFormat = function () {}; { // The format is required to process error information. If there is no format, the format can be understood as a string template validateFormat = function (format) { if (format === undefined) { throw new Error('invariant requires an error message argument'); } }; } function invariant(condition, format, a, b, c, d, e, f) { validateFormat(format); if (!condition) { var error = void 0; if (format === undefined) { // If there is no format, an unformatted error is thrown // There is a throw error in the compressed code. Please use the uncompressed development mode to obtain the complete error information error = new Error('Minified exception occurred; use the non-minified dev environment ' + 'for the full error message and additional helpful warnings.'); } else { var args = [a, b, c, d, e, f]; var argIndex = 0; // The replace method returns the return value of the callback function for each match // Generate error message error = new Error(format.replace(/%s/g, function () { return args[argIndex++]; })); // The wrong name is a violation of invariance error.name = 'Invariant Violation'; } // We don't care about the call stack of the error reporting function itself. Set the position to 1. Frame here can be understood as a frame in the call stack error.framesToPop = 1; // we don't care about invariant's own frame throw error; } }
getIteratorFn is defined here. Where recursion and flattening operations are needed later, the iterator function of the object needs to be obtained. In essence, invariant is a throw function. According to the incoming template and corresponding parameters, the% s in the template is replaced in turn to generate the final error string.
- Define various warning functions
// Depending on the implementation of invariant, we can keep the format and parameters in the construction of web // Relying on the `invariant()` implementation lets us // preserve the format and params in the www builds. // The warning implementation here is from fbjs/warning forked // The only difference we use console.warn instead of console.error When there is no native console implementation, we do nothing. This is the simplified code // This is very similar to the invariant. The only difference is that when the conditions are not met, we will print warning // This can print the absolute path of error reporting in the development environment. Removing log code in a production environment will maintain the same logic and maintain the same code path /** * Forked from fbjs/warning: * https://github.com/facebook/fbjs/blob/e66ba20ad5be433eb54423f2b097d829324d9de6/packages/fbjs/src/__forks__/warning.js * * Only change is we use console.warn instead of console.error, * and do nothing when 'console' is not supported. * This really simplifies the code. * --- * 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. */ // Low priority alarm var lowPriorityWarning = function () {}; // This bracket is used to close the context and keep the namespace clean // Can you avoid being covered in braces? { var printWarning = function (format) { // Copy into parameter group for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } var argIndex = 0; // Piecing together information var message = 'Warning: ' + format.replace(/%s/g, function () { return args[argIndex++]; }); // If you have an implementation of console that uses it if (typeof console !== 'undefined') { console.warn(message); } // Nothing try { // --- Welcome to debugging React --- // This error was thrown as a convenience so that you can use this stack // to find the callsite that caused this warning to fire. // Easy to use call stack query // For debugging react throw new Error(message); } catch (x) {} }; lowPriorityWarning = function (condition, format) { // No format information, throw wrong if (format === undefined) { throw new Error('`lowPriorityWarning(condition, format, ...args)` requires a warning ' + 'message argument'); } if (!condition) { // Copy array first for (var _len2 = arguments.length, args = Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) { args[_key2 - 2] = arguments[_key2]; } // Incoming formats and parameters printWarning.apply(undefined, [format].concat(args)); } }; } var lowPriorityWarning$1 = lowPriorityWarning; /** * 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. */ // No call stack warning var warningWithoutStack = function () {}; { warningWithoutStack = function (condition, format) { for (var _len = arguments.length, args = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) { args[_key - 2] = arguments[_key]; } if (format === undefined) { // Direct error reporting without format throw new Error('`warningWithoutStack(condition, format, ...args)` requires a warning ' + 'message argument'); } if (args.length > 8) { // Check before the condition to catch violations early. throw new Error('warningWithoutStack() currently supports at most 8 arguments.'); } // If true is ignored directly if (condition) { return; } if (typeof console !== 'undefined') { // Convert all input parameters to strings var argsWithFormat = args.map(function (item) { return '' + item; }); // Add warning header argsWithFormat.unshift('Warning: ' + format); // We intentionally don't use spread (or .apply) directly because it // breaks IE9: https://github.com/facebook/react/issues/13610 // call is not called directly because an error will be reported under ie9 // Manually throw warning // The console method will automatically replace% s with the added subsequent parameters // I can't understand this usage. Look at this article https://www.cnblogs.com/web-record/p/10477778.html Function.prototype.apply.call(console.error, console, argsWithFormat); } try { // This is the place for react debugging. It will not work normally // --- Welcome to debugging React --- // This error was thrown as a convenience so that you can use this stack // to find the callsite that caused this warning to fire. var argIndex = 0; var message = 'Warning: ' + format.replace(/%s/g, function () { return args[argIndex++]; }); // Manually throw an error throw new Error(message); } catch (x) {} }; } // Copy of warning function var warningWithoutStack$1 = warningWithoutStack; // Whether the warning status of an unmounted component is updated // A map,key is the name of the internal method of the component var didWarnStateUpdateForUnmountedComponent = {}; // Warning of no OP no operate function warnNoop(publicInstance, callerName) { { var _constructor = publicInstance.constructor; // Get the name of the component var componentName = _constructor && (_constructor.displayName || _constructor.name) || 'ReactClass'; // Alarm key var warningKey = componentName + '.' + callerName; // Updated or not if (didWarnStateUpdateForUnmountedComponent[warningKey]) { return; } // Call the method that throws the error and register at the same time to avoid secondary triggering // You cannot call a method on an unmounted component. This is a meaningless operation, // But this will trigger bug s in your application, so you can point directly to this.state Or define a state object with the class properties of the desired state in a component. warningWithoutStack$1(false, "Can't call %s on a component that is not yet mounted. " + 'This is a no-op, but it might indicate a bug in your application. ' + 'Instead, assign to `this.state` directly or define a `state = {};` ' + 'class property with the desired state in the %s component.', callerName, componentName); didWarnStateUpdateForUnmountedComponent[warningKey] = true; } }
First, we define the bottom-level printing warning method, printWarning, which also outputs error information according to format, and then we use it to assemble lowPriorityWarning. Later, we define warningWithoutStack, which carries out some parameter verification, which is essentially a call console.error But to avoid bugs caused by variables being injected, Some Sao operations are used to avoid bugs (this level of public libraries generally have similar operations), followed by warnNoop, where the alarm results are recorded through didWarnStateUpdateForUnmountedComponent to avoid repeated alarms.
- Abstract api for updating no OP queue
/** * This is the abstract API for an update queue. */ // Update Abstract api of no OP queue, queue of no operate var ReactNoopUpdateQueue = { // Check whether the composite component is mounted /** * Checks whether or not this composite component is mounted. * @param {ReactClass} publicInstance The instance we want to test. * @return {boolean} True if mounted, false otherwise. * @protected * @final */ // Input parameter is component instance isMounted: function (publicInstance) { return false; }, // Force refresh if it cannot be updated during dom changes // You may want to force a refresh of the state if you cannot use setState to update the state, // forceUpdate cannot trigger shouldComponentUpdate, but it will trigger componentWillUpdate and componentDidUpdate /** * 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 {ReactClass} publicInstance The instance that should rerender. * @param {?function} callback Called after component is updated. * @param {?string} callerName name of the calling function in the public API. * @internal */ // Warnings for queue air conditioning enqueueForceUpdate: function (publicInstance, callback, callerName) { warnNoop(publicInstance, 'forceUpdate'); }, /** * Replaces all of the state. Always use this or `setState` 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. * * @param {ReactClass} publicInstance The instance that should rerender. * @param {object} completeState Next state. * @param {?function} callback Called after component is updated. * @param {?string} callerName name of the calling function in the public API. * @internal */ // Update all States, always use this or setState to change the state, and this.state As immutable enqueueReplaceState: function (publicInstance, completeState, callback, callerName) { warnNoop(publicInstance, 'replaceState'); }, // Set a subset of the state. This provides the merge policy and does not support deep replication. Next stage: expose pending state or not use it in the merge stage /** * Sets a subset of the state. This only exists because _pendingState is * internal. This provides a merging strategy that is not available to deep * properties which is confusing. TODO: Expose pendingState or don't use it * during the merge. * * @param {ReactClass} publicInstance The instance that should rerender. * @param {object} partialState Next partial state to be merged with state. * @param {?function} callback Called after component is updated. * @param {?string} Name of the calling function in the public API. * @internal */ enqueueSetState: function (publicInstance, partialState, callback, callerName) { warnNoop(publicInstance, 'setState'); } }; // Define empty objects var emptyObject = {}; // Freeze up { Object.freeze(emptyObject); }
The abstract api for the update queue of react component is defined here. First, the instance object of react noopoupdatequeue is defined. There are isMounted,enqueueForceUpdate and other methods inside. The main purpose here is to give the definition of the method.
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. Any mistakes or omissions are welcome to discuss in the comment area. Please point out the improper translation of official notes~
Warehouse address:
React 16.8.3 source code annotation warehouse
Next:
Insight into details! react 16.8.6 source code analysis - 2 component construction and acquisition call stack