The introduction

Today, the mainstream of the front frame React, Vue and presents has become a tripartite confrontation in the field of front end, based on the development status quo of front-end technology stack, large and small companies will be more or less use of some one or more technology stack, then the master and familiar with at least one has become the front personnel essential skills to your job. Of course, the framework of part of the implementation of the details often become the focus of the interview, therefore, on the one hand in order to deal with the interviewer’s continuous questioning, on the other hand in order to improve their skills, or it is necessary to have a certain understanding of the underlying implementation principle of the framework.

Of course, there are no strict requirements for which technology stack to focus on, just pick your own favorite, in the interview, the interviewer will usually ask you which technology stack you are most familiar with, for you are not familiar with the field, the interviewer may not do too much questioning. The author has been using the Vue framework in the project, which has a low threshold to get started and provides comprehensive and friendly official documents for reference. However, React may vary from person to person. I prefer React, but I can’t say whether it is good or bad. It may be the front-end framework I first came into contact with, but unfortunately, it has not been used in my previous work. Therefore, I began to study the source code of React recently, and made a record of the interpretation process of the source code, which is convenient to deepen my memory. If you happen to have a React stack and are interested in source code, we can discuss the technical difficulties together to make the whole process of reading source code easier and more fun. If there is any misunderstanding in the source code, I want to be able to point it out.

1. Preparation

On Github, the latest version of React is v16.12.0. Fiber architecture was introduced after v16 of React, which allows tasks to be paused and recovered, breaking up large update tasks into execution units. Make full use of the idle time of the browser to execute the task in each frame, and delay the execution if there is no idle time, so as to avoid the long running of the task that will block the execution of the synchronized task in the main thread. Here in order to understand the Fiber architecture, chose a more moderate v16.10.2 version, don’t choose the latest version is the latest version of the removed some old compatible treatment scheme, although these solutions is just for compatibility, but its still more advanced ideas, is worth learning, and so its first be preserved, Another reason for choosing v16.10.2 is that React has two important optimizations in v16.10.0:

  • Transform the internal data structure of the task queue into the form of the minimum binary heap to improve the performance of the queue (in the minimum heap we can find the smallest value fastest, because that value must be at the top of the heap, effectively reducing the search time of the entire data structure).
  • Shorter life cyclepostMessageRecycle the way instead of usingrequestAnimationFrameThis way of alignment with frame boundaries (this optimization refers to the stage in which the task is delayed and then resumed), both of which are macro tasks, but there is also a sequence of macro tasks.postMessagePriority ratio ofrequestAnimationFrameHigh, which means delayed tasks can be recovered and executed more quickly).

If you don’t understand it now, there will be a separate article on task scheduling, which will be explained in detail when you encounter the above two optimisations. Before you start reading the source code, you can use create-React app to quickly build a React project. Subsequent sample code can be written on this project:

// React defaults to the latest version v16.12.0
create-react-app react-learning

// To ensure version consistency, manually change it to V16.10.2
npm install --save react@16.102. react-dom@16.102.

// Run the project
npm start
Copy the code

After the above steps, the default interface of the project should display normally in the browser, if nothing else. Thanks to the React Hooks feature released after Reactv16.8, which enabled state management in stateless function components and the use of lifecycle Hooks, even in the new create-React-app scaffolding, The root component App has been upgraded from the original writing method of class component to the recommended way of function definition component, but the original writing method of class component has not been abandoned, in fact, our project will still be filled with a lot of writing method of class component, so to understand the implementation principle of this class component, For the moment, we will write the function definition of the App root component back to the form of the class component, and make simple changes to its content:

// src -> App.js
import React, {Component} from 'react';

function List({data}) {
    return (
        <ul className="data-list">
            {
                data.map(item => {
                    return <li className="data-item" key={item}>{item}</li>})}</ul>
    );
}

export default class App extends Component {

    constructor(props) {
        super(props);
        this.state = {
            data: [1.2.3]}; } render() {return (
            <div className="container">
                <h1 className="title">React learning</h1>
                <List data={this.state.data} />
            </div>); }}Copy the code

After the above simple modifications, we then call

// src -> index.js
ReactDOM.render(<App />, document.getElementById('root'));
Copy the code

To mount the component into the DOM container, and finally get the DOM structure of the App component as follows:

<div class="container">
    <h1 class="title">React learning</h1>
    <ul class="data-list">
        <li class="data-item">1</li>
        <li class="data-item">2</li>
        <li class="data-item">3</li>
    </ul>
</div>
Copy the code

The React source code will start with the reactdom.render method, but before we do that, we need to know a few important things. These points will help us better understand the meaning of parameters and other details in the function call stack of the source code.

2. Pre-knowledge

First of all, we need to make it clear that in the above example, the App component’s render method returns an HTML structure, which is not supported in normal functions, so we usually need the corresponding plug-in to support it. React provides a Babel preset kit @babel/preset- React to support this JSX syntax, which includes two core preset plugins:

  • @babel/plugin-syntax-jsx: The purpose of this plugin is to makeBabelThe compiler can parse it correctlyjsxSyntax.
  • @babel/plugin-transform-react-jsx: After parsingjsxAfter grammar, because it is essentially a paragraphHTMLStructure, so in order to letJSThe engine is able to correctly recognize that we will need to pass the plug-injsxSyntax compilation translates to another form. By default, this is usedReact.createElementTo convert, of course we can also convert in.babelrcFile to manually set.
// .babelrc
{
    "plugins": [["@babel/plugin-transform-react-jsx", {
            "pragma": "Preact.h".// default pragma is React.createElement
            "pragmaFrag": "Preact.Fragment".// default is React.Fragment
            "throwIfNamespace": false // defaults to true}}]]Copy the code

For convenience, we can directly use Babel official laboratory to view the converted results. Corresponding to the above example, the converted results are as follows:

/ / before the conversion
render() {
    return (
        <div className="container">
            <h1 className="title">React learning</h1>
            <List data={this.state.data} />
        </div>); } render() {return react. createElement("div", {className: "content" }, React.createElement("header", null, "React learning"), React.createElement(List, { data: this.state.data })); }Copy the code

You can see that the JSX syntax is finally converted into a nested call chain consisting of the React. CreateElement methods. If you’ve seen the API before, or touched on some pseudo-code implementations, here’s a look at what it does for you, based on the source code.

2.1 the createElement method & ReactElement

In order to ensure the consistency of the source code, you are advised to keep the React version the same as that of the author. Use the V16.10.2 version and download it from Github on Facebook. After downloading it, we will open the file we need to view through the following path:

// react-16.10.2 -> packages -> react-src -> react.js
Copy the code

In the react. js file, we jump straight to line 63 and see that the React variable, as an object literal, contains a number of well-known methods, including the React Hooks method introduced after v16.8:

const React = {
  Children: {
    map,
    forEach,
    count,
    toArray,
    only,
  },

  createRef,
  Component,
  PureComponent,

  createContext,
  forwardRef,
  lazy,
  memo,

  // Some useful React Hooks methods
  useCallback,
  useContext,
  useEffect,
  useImperativeHandle,
  useDebugValue,
  useLayoutEffect,
  useMemo,
  useReducer,
  useRef,
  useState,

  Fragment: REACT_FRAGMENT_TYPE,
  Profiler: REACT_PROFILER_TYPE,
  StrictMode: REACT_STRICT_MODE_TYPE,
  Suspense: REACT_SUSPENSE_TYPE,
  unstable_SuspenseList: REACT_SUSPENSE_LIST_TYPE,

  // Use the latter in production mode
  createElement: __DEV__ ? createElementWithValidation : createElement,
  cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
  createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
  isValidElement: isValidElement,

  version: ReactVersion,

  unstable_withSuspenseConfig: withSuspenseConfig,

  __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
Copy the code

Here we’ll focus on the createElement method for the moment. In production mode it comes from the ReactElement. Js file at the same level as React. You can see the function definition for the createElement method (with some code executed only by the __DEV__ environment removed) :

/** * This method takes, but is not limited to, three arguments, corresponding to the converted JSX syntax in the example above. * @param type represents the type of the current node, which can be a native DOM tag string, @param config represents the property configuration information of the current node. @param children represents the children of the current node. You can pass in the original string text without passing it. It is even possible to pass multiple child nodes * @returns a ReactElement object */
export function createElement(type, config, children) {
  let propName;

  // Reserved names are extracted
  // It is used to store attributes in config, but some internally protected attribute names are filtered
  const props = {};

  // Save the key and ref attributes in config as separate variables
  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  // if config is null, no related attributes are set on the node
  if(config ! =null) {

    Config.ref! == undefined
    if (hasValidRef(config)) {
      ref = config.ref;
    }

    Config.key! == undefined
    if (hasValidKey(config)) {
      key = ' ' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;

    // Remaining properties are added to a new props object
    // Copy all properties in config to the props object after filtering out the internally protected properties
    // const RESERVED_PROPS = {
    // key: true,
    // ref: true,
    // __self: true,
    // __source: true,
    // };
    for (propName in config) {
      if( hasOwnProperty.call(config, propName) && ! RESERVED_PROPS.hasOwnProperty(propName) ) { props[propName] = config[propName]; }}}// Children can be more than one argument, and those are transferred onto
  // the newly allocated props object.
  // Since there is no limit to the number of child nodes, start with the third parameter and determine the length of the remaining parameters
  // With multiple children, the props. Children property is stored as an array
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    
    // In the single-node case, the props. Children property stores the corresponding node directly
    props.children = children;
  } else if (childrenLength > 1) {
    
    // Create an array based on the number of children in the multi-node case
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray;
  }

  // Resolve default props
  // This is used to resolve the static attribute defaultProps
  // In the case of class components or function-defined components, the static attribute defaultProps can be set separately
  // If defaultProps is set, traverse each property and assign it to the props object (if the corresponding property in the props object is undefined)
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) { props[propName] = defaultProps[propName]; }}}// Finally returns a ReactElement object
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
Copy the code

After the above analysis, we can conclude that the render method of the class component finally returns a multi-layer nested structure composed of multiple ReactElement objects, and all the child node information is stored in the props. Children property of the parent node. We locate the source at line 111 of ReactElement. Js and see the full implementation of the ReactElement function:

/** * is a factory function that creates and returns a ReactElement object on each execution. As with the first parameter of the react. createElement method, @param key is the unique identifier of the node. In general, we need to set the key attribute for each node. @param ref refers to the node. You can use React. CreateRef () or useRef() to create a reference * @param self this property only exists in the development environment * @param source this property only exists in the development environment * @param owner an internal property, Point to ReactCurrentOwner. Current, said a Fiber node * @ param props said the node attribute information, in the React. Through the config in the createElement method, The children parameter and the static property of defaultProps get * @ RETURNS a ReactElement object */
const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // This tag allows us to uniquely identify this as a React Element
    // Just add one? Typeof property, which identifies this as a React Element
    ?typeof: REACT_ELEMENT_TYPE,

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

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

Is the structure of a ReactElement object relatively simple? The Typeof property is used to identify the object as a React Element type. REACT_ELEMENT_TYPE is a Symbol type in environments that support Symbol type, otherwise a value of type number. There are many other types that correspond to REACT_ELEMENT_TYPE, all of which are stored in the shared/ReactSymbols directory. We can only focus on this type for the moment, and we will look at other types later.

2.2 Component & PureComponent

Once we understand the structure of the ReactElement object, let’s go back to the previous example where we modified the App component to a class component by inheriting React.component. let’s take a look at the underlying implementation of React.component. react.ponent. React.Com ponent source in packages/react/SRC/ReactBaseClasses js file, we will source localization to line 21, can see the complete implementation of Component constructor:

The @param props function is used to create an instance of a class component. @param props is used to hold property information. @param context is used to hold context information. Used to process subsequent update scheduling tasks */
function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  // This property is used to store the reference information of the class component instance
  // React allows you to create references in several ways
  
   this.inputRef = input; } / >
  // Use the react.createref () method, for example: this.inputref = react.createref (null); 
  // Use the useRef() method, for example: this.inputref = useRef(null); 
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  // When state changes, the updater object is required to handle subsequent update scheduling tasks
  // This part is related to the task scheduling content, in the follow-up analysis to the task scheduling stage
  this.updater = updater || ReactNoopUpdateQueue;
}

// Added an isReactComponent property on the prototype to identify the instance as an instance of a class component
// How to distinguish a function-defined component from a class component
// Function definition components do not have this property, so you can distinguish it by determining whether the prototype has this property
Component.prototype.isReactComponent = {};

/** * used to update the state * @param partialState specifies the state to be updated next time * @param callback specifies the callback to be performed after the component is updated */
Component.prototype.setState = function(partialState, callback) {... this.updater.enqueueSetState(this, partialState, callback, 'setState');
};

/** * used to force rerendering * @param callback needs to be executed after the component is rerendered */
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
Copy the code

Task scheduling will be covered later in the scheduling phase, but we now know that we can distinguish function-defined components from class components by the isReactComponent property on the prototype. In fact, this attribute is used in the source code to distinguish Class Component from Function Component. You can find the following method:

// Return true to represent the class component, otherwise to represent the function definition component
function shouldConstruct(Component) {
  return!!!!! (Component.prototype && Component.prototype.isReactComponent); }Copy the code

In addition to the Component constructor, there is a familiar PureComponent constructor that uses a shallow comparison to determine whether properties passed before and after a Component have changed to determine whether the Component needs to be rerendered, partly to avoid performance problems caused by Component rerendering. Also, in the reactBaseclasses.js file, let’s look at the underlying implementation of PureComponent:

// Implement typical parasitic combinatorial inheritance by borrowing constructors to avoid prototype contamination
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

function PureComponent(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  this.updater = updater || ReactNoopUpdateQueue;
}

// Point the PureComponent prototype to an instance of the borrowed constructor
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());

// Redirects the constructor
pureComponentPrototype.constructor = PureComponent;

// Avoid an extra prototype jump for these methods.
// Combine Component.prototype and pureComponent. prototype to reduce the time wasted in prototype chain searches (the longer the chain is, the longer it takes)
Object.assign(pureComponentPrototype, Component.prototype);

PureComponent has an isPureReactComponent property on its prototype
pureComponentPrototype.isPureReactComponent = true;
Copy the code

Based on the above analysis, we can draw a preliminary difference between Component and PureComponent, which can be distinguished by judging whether the prototype has the isPureReactComponent attribute. Of course, more fine-grained differentiation will be determined after reading the subsequent source code content.

3. Interview test points

Here are some possible interview questions that should not be a problem, or at least you won’t be unable to answer any of them. Give them a try:

  • Why is React supportedjsxgrammar
  • Class of the componentrenderWhat is the final result returned after the method is executed
  • Handwritten code to implement onecreateElementmethods
  • How do you determine if an object isReact Element
  • How to classify components and function definition components
  • ComponentandPureComponentThe relationship between
  • How to distinguish betweenComponentandPureComponent

4, summarize

This article begins with a few basic pre-knowledge points that will help us better understand the source code when we analyze the task scheduling and rendering process of the component. Reading source code is a pain in the neck, partly because of the sheer volume of source code and the complexity of file dependencies, and partly because it is a long process that can take time away from learning other new technologies. But in fact, we need to understand that learning source code is not just for interviews, there are a lot of design patterns or techniques we can use for reference, if we can learn and apply to the project we are doing, it is also a meaningful thing. The following articles will start with the reactdom.render method and analyze the whole process of component rendering step by step. We don’t need to understand every line of code, after all, everyone has different ideas, but we still need to spend more time to understand the key steps.

5, communication

If you think the content of this article is helpful to you, can you do a favor to pay attention to the author’s public account [front-end], every week will try to original some front-end technology dry goods, after paying attention to the public account can invite you to join the front-end technology exchange group, we can communicate with each other, common progress.

The article has been updated toMaking a blog, if your article is ok, welcome to STAR!

One of your likes is worth more effort!

Grow up in adversity, only continue to learn, to become a better yourself, with you!