React 17.0.0-RC.2 was released recently and brings new features about JSX:
- with
jsx()
Function to replaceReact.createElement()
- Automatically is introduced into
jsx()
function
An example is the following code
// hello.jsx
const Hello = () = > <div>hello</div>;
Copy the code
Will be translated into
// hello.jsx
import {jsx as _jsx} from 'react/jsx-runtime'; // Automatically imported by the compiler
const Hello = () = > _jsx('div', { children: 'hello' }); / / not React. The createElement method
Copy the code
React provides instructions on how to use the new translation syntax and automatic migration tools
I’m all for this change. What interests me most, though, is why use the new conversion syntax? The reason can be found in the React RFC-0000 documentation.
motivation
React was originally designed around class components, but function components have become increasingly popular thanks to hooks. Some of the design considerations for class components are not appropriate for functional components, and new concepts must be introduced for developers to understand.
For example 🌰, the ref feature is normal for class components, and we can get an instance of a class component by using ref. It makes no sense to pass ref to function components that do not have hooks in front of them, as we all know function components have no instances. Function components behave almost exactly like class components after hooks are implemented, and React provides useImperativeHandle() hooks that allow function components to expose their own methods to their parent. However, we can’t easily do this because React handles refs.
React blocks the REF attribute in the Props object, and then mounts and unmounts the ref attribute itself. For functional components, however, this mechanism is a little out of place. Because of the blocking, you can’t get the ref from props, you have to somehow tell React I need the ref, so React introduces the forwardRef() function to do that.
// For function components, we want to do this
const Input = (props) = > <input ref={props.ref} /> // error props. Ref is undefined
// But now we have to write it this way
const Input = React.forwardRef((props, ref) = > <input ref={ref} />)
Copy the code
So RFC-0000 proposed to revisit some of the original designs to see if they could be simplified
React.createElement()
The problem of
React. CreateElement () was a balanced choice when React implemented the JSX solution. At that point, it worked fine, and many alternatives didn’t show enough advantage to replace it
Creating ReactElement with reac.createElement () in a React application is a very frequent operation, because the corresponding ReactElement is recreated each time it is rerendered
As technology evolved, the react.createElement () design exposed a number of problems:
- Each time
React.createElement()
, dynamically check whether a component exists.defaultProps
Property, which causes the JS engine to be unable to optimize this because the logic is highly complex .defaultProps
对React.lazy
It doesn’t work. Because the default assignment to the component props occurs atReact.createElement()
During the period,lazy
Need to wait for asynchronous componentsresolved
. This causes React to have to assign the props object by default at render time, which makeslazy
The component’s.defaultProps
Is inconsistent with the semantics of other components- Children is passed in dynamically as a parameter, so its shape cannot be determined directly, so it must be in
React.createElement()
And put it all together - call
React.createElement()
Is a dynamic attribute lookup process, rather than a variable lookup confined to the module, which requires additional cost to perform the lookup operation - The props object passed could not be sensed if it was a mutable object created by the user, so it must be cloned again
key
å’Œref
All from the props object, so if we don’t clone a new object, we have to clone it on the props object we passeddelete
掉key
å’Œref
Property, however, which makes the props object map-like, which is bad for engine optimizationkey
å’Œref
Can be achieved by.
The extension operator is passed, which makes it impossible to determine this without complex parsing<div {... props} />
Mode, there is no transferkey
å’Œref
- JSX translation functions depend on variables
React
Exists in scope, so you must import the module’s default exports
In addition to performance considerations, RFC-0000 will make it possible to simplify or eliminate React concepts such as forwardRef and defaultProps in the near future, reducing the cost of getting started
In addition, in order to standardize JSX syntax one day, it is necessary to decouple JSX from React coupling
Changes to the JSX transformation process
Automatic introduction (already installed)
React is no longer introduced into the scope manually, but is introduced automatically by the compiler
function Foo() {
return <div />;
}
Copy the code
Will be translated into
import {jsx} from "react";
function Foo() {
return jsx('div',...). ; }Copy the code
Pass key as a parameter (installed)
To understand what this means, we need to look at the function signatures of the new conversion functions JSX and jsxDev:
function jsxDEV(type, config, maybeKey, source, self)
function jsx(type, config, key)
As you can see, passing key as a parameter means exactly what it means. For better understanding, let’s look at the example 🌰
// test.jsx
const props = {
value: 0.key: 'bar'
};
<div key="foo" {. props} >
<span>hello</span>
<span>world</span>
</div>;
<div {. props} key="foo">
<span>hello</span>
<span>world</span>
</div>;
Copy the code
For the above code, the traditional transformation would translate into the following code
const props = {
value: 0.key: 'bar'
};
React.createElement("div".Object.assign({ key: "foo" }, props),
React.createElement("span".null."hello"),
React.createElement("span".null."world"));
React.createElement("div".Object.assign({}, props, { key: "foo" }),
React.createElement("span".null."hello"),
React.createElement("span".null."world"));
Copy the code
For the new translation process, the following code is translated
import { createElement as _createElement } from "react";
import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
// Note that all of the above code is automatically introduced
const props = {
value: 0.key: 'bar'
};
_jsxs(
"div",
{
...props,
children: [_jsx("span", {
children: "hello"
}), _jsx("span", {
children: "world"}})],"foo".// as an argument
);
_createElement(
"div",
{
...props,
key: "foo" // Still as part of props
}, _jsx("span", {
children: "hello"
}), _jsx("span", {
children: "world"}));Copy the code
You can see that for
In fact, the compatible code is very simple, so let’s look at it a little bit
function jsxDEV(type, config, maybeKey, source, self) {{var propName; // Reserved names are extracted
var props = {};
var key = null;
var ref = null; // Currently, key can be spread in as a prop. This causes a potential
// issue if key is also explicitly declared (ie.
// or
). We want to deprecate key spread,
// but as an intermediary step, we will use jsxDEV for everything except
//
, because we aren't currently able to tell if
// key is explicitly declared to be undefined or not.
if(maybeKey ! = =undefined) {
key = ' ' + maybeKey;
}
if (hasValidKey(config)) {
key = ' ' + config.key;
}
// ... codes}}Copy the code
The compatibility logic is very simple, and in the notes we can see the detailed explanation, and we are now in an “intermediary step”
Pass Children as props
Let’s take a look at some examples, and then we’ll talk about why we’re doing this. Okay
For the following code
const a = 1;
const b = 1;
<div>{a}{b}</div>;
Copy the code
The traditional transformation will translate to
const a = 1;
const b = 1;
React.createElement(
"div".null,
a,
b,
);
Copy the code
New transformation logic
import { jsxs as _jsxs } from "react/jsx-runtime";
const a = 1;
const b = 1;
_jsxs("div", {
children: [a, b] / / here
});
Copy the code
The traditional conversion process would be to pass Children as function parameters, but that would require the conversion function to assemble an array of parameters internally
function createElement(type, config, children) {
// ... codes
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
// ... codes
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
Copy the code
This step can be very costly, especially since creating ReactElement is a frequent operation, which makes the performance penalty even worse
By passing Children as props, we can know the shape of Children in advance! Without having to go through another expensive splicing operation
Transformation logic in DEV mode
Function jsxDEV(type, config, maybeKey, source, self) is implemented for DEV mode. Function jsxDEV(type, config, maybeKey, source, self) The difference is that DEV mode passes two more arguments source and self
Always open (loaded)
Traditional translation logic has a special pattern. In most cases, traditional translation processes clone props, but
mode. The traditional mode translates to React. CreateElement (‘div’, props). Because createElement internally clones props, this translation is harmless
React doesn’t want to clone props in the new JSX conversion function, so
will always translate to JSX (‘div’, {… props})
The end of the
In addition, rfC-0000 also describes the changes to ref, forwardRef,.defaultprops and other related concepts.
Even the latest JSX transformation logic is actually an intermediate process with a lot of compatibility code, and the ultimate goal of RFC-0000 is to implement JSX () functions as follows
function jsx(type, props, key) {
return {
$$typeof: ReactElementSymbol,
type,
key,
props,
};
}
Copy the code
Similarly, we can look at the implementation logic of the JSX () function in production mode
function q(c, a, k) {
var b, d = {}, e = null, l = null;
void 0! == k && (e ="" + k);
void 0! == a.key && (e ="" + a.key);
void 0! == a.ref && (l = a.ref);for (b ina) n.call(a, b) && ! p.hasOwnProperty(b) && (d[b] = a[b]);if (c && c.defaultProps) for (b in a = c.defaultProps, a) void 0 === d[b] && (d[b] = a[b]);
return { $$typeof: g, type: c, key: e, ref: l, props: d, _owner: m.current }
}
Copy the code
You can see that the implementation is now very close to the RFC-0000 goal