React.createElement method source code analysis (detailed principle)

Summary

As mentioned in the previous article, there are two ways to create elements in React:
the first is to create through JSX, and the second is to create through React.createElement method. However, the way to create React elements through JSX will eventually be transformed by Babel, and then React.createElement will be used to create elements .

And this article mainly talks about how React.createElement creates React elements.

1. Create method

In order to understand the method of React.createElement, we manually implement a simple version by ourselves.

OK, first we create a React element through the React.createElement method and print it out:

    const b = React.createElement('div',{
    
    data: '1234'},'oneTwo')
    console.log(b);

The printed result is:
insert image description here
So, if we want to implement the React.createElement method, we can write it like this when defining it:

function createElement(type, config, children){
    
    

  let props = {
    
    }
  let myType = ''
  let key = ''
  let ref = ''

  return {
    
    
    $$typeOf: 'react.element',
    type: myType,
    props,
    key,
    ref
  }

}

Here we don't care about the variable **$$typeOf**, only consider other variables, and the final returned object should be this data structure.

2. Processing type

Well, the definition of the method has been written. For the three parameters passed in, let's look at them one by one.

The first parameter type, which represents the type of React element created.
There can be traditional HTML elements, such as div, h1, span and other tags.
It can also be a React component (note here).

And React has two ways to create components, so the value of type has three data types:

(1) String: such as "div", "h1", "span", "p" and other labels
(2) Function: Create React components by function
(3) Class: Create React components by class

And this time there is a problem!

class Demo {
    
    

}

typeof Demo

What should the above value be? The answer is function, so here we only need to consider the two types of type as string and function.

So we can write a method to judge the type:

function isValidElementType(type){
    
    
  if(typeof type === 'string' || typeof type === 'function'){
    
    
    return true;
  }
  
  return false;
}

Referenced in our main method:

function createElement(type, config, children){
    
    

  let props = {
    
    }
  let myType = ''
  let key = ''
  let ref = ''

  if(isValidElementType(type)){
    
    
    myType = type;
  }

  return {
    
    
    $$typeOf: 'react.element',
    type: myType,
    props,
    key,
    ref
  }

}

3. Processing config

For the second parameter of the React.createElement method, an object is received. And all properties under the object will be placed in props.

Is this sentence right? In fact, it is not quite right, there are special cases, if there are two attributes of key or ref under this object. It will not be placed in props, but directly assigned to key and ref

Ok, with the above words, we can process the config:

  if(config != null){
    
    
    if(config.ref){
    
    
      ref = config.ref
    }
    if(config.key){
    
    
      key = config.key
    }

    for(let propName in config){
    
    
      if(!(['ref','key'].includes(propName))){
    
    
        props[propName] = config[propName]
      }
    }
  }

We only need to give the key and ref of config to the key and ref in our returned object. To facilitate the config again, the properties and values ​​​​taken out exclude key and ref. Finally our config property is handled.

4. Dealing with children

The last step is to deal with the children property. The third parameter of the React.createElement method can also be the fourth parameter (that is, all subsequent parameters). Both can be strings, or React elements.

The React element here can be created regardless of whether it is created through JSX or through the React.createElement method

There are two types of parameters:
the first is that there are only three parameters, that is, children is a value. At this time, the children in props is the string.
The second is that there are more than three parameters. At this time, the children in props is an array, and the elements in the array are all the parameters that follow.

OK, with the above foundation, children can be processed:

  let childrenLength = arguments.length - 2
  if(childrenLength === 1){
    
    
    props.children = children
  }else if(childrenLength > 1){
    
    
    let children = new Array(childrenLength)
    for(let i = 0;i<childrenLength;i++){
    
    
      children[i] = arguments[i+2]
    }
    props.children = children
  }

Here, arguments are used to judge the number of parameters, and then determine the branch condition.
Then according to the situation, determine the children in props.

Finally, paste the complete createElement method (simple version):

function createElement(type, config, children){
    
    

  let props = {
    
    }
  let myType = ''
  let key = ''
  let ref = ''

  if(isValidElementType(type)){
    
    
    myType = type;
  }

  if(config != null){
    
    
    if(config.ref){
    
    
      ref = config.ref
    }
    if(config.key){
    
    
      key = config.key
    }

    for(let propName in config){
    
    
      if(!(['ref','key'].includes(propName))){
    
    
        props[propName] = config[propName]
      }
    }
  }

  let childrenLength = arguments.length - 2
  if(childrenLength === 1){
    
    
    props.children = children
  }else if(childrenLength > 1){
    
    
    let children = new Array(childrenLength)
    for(let i = 0;i<childrenLength;i++){
    
    
      children[i] = arguments[i+2]
    }
    props.children = children
  }

  return {
    
    
    $$typeOf: 'react.element',
    type: myType,
    props,
    key,
    ref
  }

}

5. Compare the real React.createElement source code

OK, the above only implements a relatively simple React.createElement method, but after understanding the process, we can take a look at the real React.createElement source code:

isValidElementType method

function isValidElementType(type) {
    
    
  if (typeof type === 'string' || typeof type === 'function') {
    
    
    return true;
  } // Note: typeof might be other than 'symbol' or 'number' (e.g. if it's a polyfill).


  if (type === REACT_FRAGMENT_TYPE || type === REACT_PROFILER_TYPE || enableDebugTracing  || type === REACT_STRICT_MODE_TYPE || type === REACT_SUSPENSE_TYPE || type === REACT_SUSPENSE_LIST_TYPE || enableLegacyHidden  || type === REACT_OFFSCREEN_TYPE || enableScopeAPI  || enableCacheElement  || enableTransitionTracing ) {
    
    
    return true;
  }

  if (typeof type === 'object' && type !== null) {
    
    
    if (type.$$typeof === REACT_LAZY_TYPE || type.$$typeof === REACT_MEMO_TYPE || type.$$typeof === REACT_PROVIDER_TYPE || type.$$typeof === REACT_CONTEXT_TYPE || type.$$typeof === REACT_FORWARD_REF_TYPE || // This needs to include all possible module reference object
    // types supported by any Flight configuration anywhere since
    // we don't know which Flight build this will end up being used
    // with.
    type.$$typeof === REACT_MODULE_REFERENCE || type.getModuleId !== undefined) {
    
    
      return true;
    }
  }

  return false;
}

The main thing here is to judge the type of type, but there are several more element types that come with React.

createElement method

function createElement(type, config, children) {
    
    
  var propName; // Reserved names are extracted

  var props = {
    
    };
  var key = null;
  var ref = null;
  var self = null;
  var source = null;

  if (config != null) {
    
    
    if (hasValidRef(config)) {
    
    
      ref = config.ref;

      {
    
    
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }

    if (hasValidKey(config)) {
    
    
      {
    
    
        checkKeyStringCoercion(config.key);
      }

      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

    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.


  var childrenLength = arguments.length - 2;

  if (childrenLength === 1) {
    
    
    props.children = children;
  } else if (childrenLength > 1) {
    
    
    var childArray = Array(childrenLength);

    for (var i = 0; i < childrenLength; i++) {
    
    
      childArray[i] = arguments[i + 2];
    }

    {
    
    
      if (Object.freeze) {
    
    
        Object.freeze(childArray);
      }
    }

    props.children = childArray;
  } // Resolve default props


  if (type && type.defaultProps) {
    
    
    var defaultProps = type.defaultProps;

    for (propName in defaultProps) {
    
    
      if (props[propName] === undefined) {
    
    
        props[propName] = defaultProps[propName];
      }
    }
  }

  {
    
    
    if (key || ref) {
    
    
      var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;

      if (key) {
    
    
        defineKeyPropWarningGetter(props, displayName);
      }

      if (ref) {
    
    
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }

  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
};

This method mainly deals with config and children.

The rest of the parts are not sticky, if you are interested in the source code, you can break it and try it yourself!

Guess you like

Origin blog.csdn.net/weixin_46726346/article/details/126471596