preface

What is JSX? Here’s a quick look at the React definition. It says that JSX is a good way to describe how the UI should behave in the way it should interact, that is, let us write React components like WE write HTML. In addition to native HTML tags, you can customize your own components to build complex pages step by step. How React dealt with JSX in this process, we will disentangle it one by one and explore the essence behind it through the phenomenon.

JSX = JSX = JSX = JSX

## import React, index.js
yarn dev or npm run dev
#Import React, index-jsx-runtime.js
yarn runtime or npm run runtime
#Jsx-runtime, index-feact-jsx-runtime.js
yarn feact or npm run feact
Copy the code

import React from ‘react’

Before React17, if we didn’t explicitly call import React from ‘React’

The following error will be reported on the page

Of course, when we first started using React, both our predecessors and books said that we must display ‘import React from’ React ‘at the top. Over time, we would reflexibly write such a sentence. But have you ever wondered why you have to do this so that the page doesn’t report an error warning? I didn’t use React.

Also, you’ll notice that every time you write ‘import React from’ React ‘, the import React in vscode will be highlighted, while the import eslint in vscode will be dark. And the prompt is declared but not used

Hey, why are these two questions so weird? Below, let us incarnate Sherlock Holmes to reveal the secrets ~

React.createElement

Babel translates JSX into a function call called react.createElement (). When we write a component that uses JSX, Babel will go to React. CreateElement to translate JSX into the corresponding object.

An 🌰 :

function App() {
  return <div className='.app'>app</div>
}

console.log('App: ', App)
console.log('<App/>: '.<App/>)
Copy the code

If we print App directly above, then App is essentially a function, whereas if we use
, which calls a component, we can see the difference in the console

Babel translates
into the following code:

The React. The createElement method (Æ’ App (),Æ’ App(
  {},       // config
  undefined // children
);
Copy the code

Div is then translated into the following code:

React.createElement(
  'div'.// type
  { className: ".app" }, // config
  'app'                  // children
);
Copy the code

So let’s look at the implementation of createElement

export function createElement(type, config, children) {

  let propName;

  // Reserved names are extracted
  const props = {};
   
  let key = null;
  let ref = null;
  let self = null;
  let source = null;
   
  if(config ! =null) {
    // 1. Check that ref and key are intercepted if they both meet the requirements
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = ' ' + config.key;
    }
    __self and __source are omitted below.// 2. Handle props except ref and key
    for (propName in config) {
      if( hasOwnProperty.call(config, propName) && ! RESERVED_PROPS.hasOwnProperty(propName) ) { props[propName] = config[propName]; }}}// 3
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }

    props.children = childArray;
  }

  // 4. Handle defaultProps
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) { props[propName] = defaultProps[propName]; }}}// 5. Finally call ReactElement
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
Copy the code

Let’s analyze the code process above

1. Intercept refs and keys

if (hasValidRef(config)) {
  ref = config.ref;
}
if (hasValidKey(config)) {
  key = ' ' + config.key;
}
Copy the code

If the ref or key meets the requirements, then we can extract them separately, not in the props below. This also verifies that when we pass a prop from the parent to the child ref or key, we cannot retrieve it in the child because it is intercepted in the first place

2. Handle props except ref, key, __self, and __source

for (propName in config) {
  if (
    /** * hasownProperty. call(config, propName); /** * hasownProperty. call(config, propName); /** * hasownProperty. call(config, propName); The latter two are for development only and can be ignored
    hasOwnProperty.call(config, propName) &&
    !RESERVED_PROPS.hasOwnProperty(propName)
  ) {
    props[propName] = config[propName];
  }
}
Copy the code

3. Deal with the children

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

See createElement (type, config, children) takes three arguments, but a parent can actually have multiple children. The div below has two children

  <div className=".app">
    <span>span1</span>
    <span>span2</span>
  </div>
Copy the code

Babel then converts to the following code

React.createElement(
  'div'.// type
  { className: ".app" }, // config
  {
    $$typeof: Symbol(react.element),
    key: null.props: {children: 'span1'},
    ref: null.type: "span"}, {$$typeof: Symbol(react.element),
    key: null.props: {children: 'span2'},
    ref: null.type: "span"});Copy the code

That is, if there are more than one child, each child is passed in order after the second argument (createElement is used to process the child first, then the parent, so we can see that the passed child has already been processed).

That’s why the following code takes the length of arguments minus 2 to get the actual number of children passed in

const childrenLength = arguments.length - 2;
Copy the code

Of course you can use the ES6 here… The children to replace

If there is only one child, assign it directly to props. Children, otherwise put all the children in the array, and then put them on props. Children

if (childrenLength === 1) {
   // There is only one child, which is directly assigned to props. Children
   props.children = children;
} else if (childrenLength > 1) {
   // Otherwise put all 'children' into the array
   const childArray = Array(childrenLength);
   for (let i = 0; i < childrenLength; i++) {
       childArray[i] = arguments[i + 2];
   }
   props.children = childArray;
}
Copy the code

4. Handle defaultProps

If I pass defaultProps, for example

MyComponent.defaultProps = { prop1: 'x'.prop2: 'xx'. }Copy the code

If a component prop is not passed, or if a component prop is passed but the value is undefined, then the prop corresponding to defaultProps is passed

if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) { props[propName] = defaultProps[propName]; }}}Copy the code

If we pass prop1 null to the component, then we get null instead of prop1: ‘x’ on defaultProps.

5. Finally, call ReactElement

ReactElement has very little code, which simply combines the passed parameters into an Element object and returns it

const ReactElement = function(type, key, ref, self, source, owner, props) {
  // self, source for DEV, skipped here
  const element = {
    $$typeof: REACT_ELEMENT_TYPE,
    type: type,
    key: key,
    ref: ref,
    props: props,
  };

  return element;
};
Copy the code
  • $$typeof: REACT_ELEMENT_TYPERepresents the ReactElement type, essentially oneSymbolValue forSymbol.for('react.element')
  • typeIs the component type, and for native DOM,typeIt’s going to be correspondingtag, such asdiv.spanEtc., for function componentsFnCompComponents, classes,ClassCompIs the corresponding function or class, such asÆ’ FnComp ().class ClassA

import ReactWhy highlight

This is because in (j | t) sconfig. Json default inside a configuration:

If you change it to your own feact. createElement, such as “jsxFactory”: “feact. createElement”, then we can see that the original import React will not be highlighted

However, this is just a display function at the editor level. To really work, you have to configure Babel

jsx-runtime

React17 provides a new version of the JSX transformation, which eliminates the need to explicitly import React. React and Babel have created @babel/plugin-transform-react-jsx. The plugin will default to react JSX – the runtime in the directory. The js file read corresponding JSX, interpret the corresponding JSX JSXS etc. Function

The JSX function is basically the same as createElement, except that children are handled in createElement, while children are handled directly in config.children in JSX without processing

function App() {
  return <div className=".app"> 
    <span>span1</span>
    <span>span2</span>
  </div>
}
Copy the code

Customize your own JSX-Runtime

If you decide you’re ready to write a react-like library, such as feact, you can provide JSX, JSXS, etc., in feact/jsx-runtime.js

Then you must add importSource to @babel/plugin-transform-react-jsx as feact in webpack.config.js

plugins: [
    [require.resolve('@babel/plugin-transform-flow-strip-types')],
    ['@babel/plugin-transform-react-jsx', {runtime:'automatic'.importSource: 'feact'}]]Copy the code

conclusion

  1. 😯, originally must be explicit before Act17import ReactbecauseBabelThe default will beJSXthroughReact.createElementTranslation, if not introducedReactThen you can’t get itcreateElement, it is natural to report an error
  2. 😯,JSXWill beBabelTranslate to the following objects
{ 
    $$typeof: Symbol(react.element),
    key: null.props: {children: 'span1'},
    ref: null type: "span",}Copy the code
  1. 😯, where Babel translates child nodes first and then parent nodes

  2. 😯 : React17 doesn’t import React because @babel/plugin-transform-react-jsx automatically imports JSX and JSXS from React /jsx-runtime.js

  3. 😯 to customize jSX-Runtime, add importSource: ‘YourPackage’ and export JSX and JSXS from YourPackage/ jSX-Runtime

  4. 😯, createElement handles children. JSX handles children directly from config.children

The last

Thank you for leaving your footprints. If you think the article is good 😄😄, please click 😋😋, like + favorites + forward 😄

Let the React fly series

Translation translation, what is ReactDOM. CreateRoot