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
Babel translates
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_TYPE
Represents the ReactElement type, essentially oneSymbol
Value forSymbol.for('react.element')
type
Is the component type, and for native DOM,type
It’s going to be correspondingtag
, such asdiv
.span
Etc., for function componentsFnComp
Components, classes,ClassComp
Is the corresponding function or class, such asÆ’ FnComp ()
.class ClassA
import React
Why 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
- 😯, originally must be explicit before Act17
import React
becauseBabel
The default will beJSX
throughReact.createElement
Translation, if not introducedReact
Then you can’t get itcreateElement
, it is natural to report an error - 😯,
JSX
Will beBabel
Translate to the following objects
{
$$typeof: Symbol(react.element),
key: null.props: {children: 'span1'},
ref: null type: "span",}Copy the code
-
😯, where Babel translates child nodes first and then parent nodes
-
😯 : React17 doesn’t import React because @babel/plugin-transform-react-jsx automatically imports JSX and JSXS from React /jsx-runtime.js
-
😯 to customize jSX-Runtime, add importSource: ‘YourPackage’ and export JSX and JSXS from YourPackage/ jSX-Runtime
-
😯, 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