前言
书接上文,我们已经了解了jsx是如何编译成jsx方法的过程了,效果如图:
// 编译前
<div key="key" ref="ref" name="name">
<div key="key1" ref="ref1" name="name1">1</div>
<div key="key2" ref="ref2" name="name2">2</div>
</div>
// 编译后
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/*#__PURE__*/_jsxs("div", {
ref: "ref",
name: "name",
children: [/*#__PURE__*/_jsx("div", {
ref: "ref1",
name: "name1",
children: "1"
}, "key1"), /*#__PURE__*/_jsx("div", {
ref: "ref2",
name: "name2",
children: "2"
}, "key2")]
}, "key");
那么react中的jsx方法到底是干什么的呢?我们这一章来探索一下react中的jsx方法实现
目录:packages/react/src/jsx/ReactJSX.js
jsx方法实现
入口
代码在packages/react/src/jsx/ReactJSX.js中,其中导出了Fragment,jsx,jsxs,jsxDev,其实刚好就对应上了我们上一节中说过的jsx编译之后的那几个方法。
import {REACT_FRAGMENT_TYPE} from 'shared/ReactSymbols';
import {
jsxProd,
jsxProdSignatureRunningInDevWithDynamicChildren,
jsxProdSignatureRunningInDevWithStaticChildren,
jsxDEV as _jsxDEV,
} from './ReactJSXElement';
const jsx: any = __DEV__
? jsxProdSignatureRunningInDevWithDynamicChildren
: jsxProd;
const jsxs: any = __DEV__
? jsxProdSignatureRunningInDevWithStaticChildren
: jsxProd;
const jsxDEV: any = __DEV__ ? _jsxDEV : undefined;
export {REACT_FRAGMENT_TYPE as Fragment, jsx, jsxs, jsxDEV};
环境判断
我们可以看到这里的jsx方法是有开发版本和生产版本的,根据__DEV__这个标识来判断是开发环境还是生产环境,为true则是开发环境,反之则是生产环境,这个__DEV___是怎么来的呢?
我们可以找到项目的rollup打包配置,目录在scripts/rollup/build.js,是根据配置的bundleType得到是否是开发模式,然后通过@rollup/plugin-replace插件进行对__DEV__全局替换。
const replace = require('@rollup/plugin-replace');
function isProductionBundleType(bundleType) {
switch (bundleType) {
case NODE_ES2015:
return true;
case ESM_DEV:
case NODE_DEV:
case BUN_DEV:
case FB_WWW_DEV:
case RN_OSS_DEV:
case RN_FB_DEV:
return false;
case ESM_PROD:
case NODE_PROD:
case BUN_PROD:
case NODE_PROFILING:
case FB_WWW_PROD:
case FB_WWW_PROFILING:
case RN_OSS_PROD:
case RN_OSS_PROFILING:
case RN_FB_PROD:
case RN_FB_PROFILING:
case BROWSER_SCRIPT:
return true;
default:
throw new Error(`Unknown type: ${bundleType}`);
}
}
const isProduction = isProductionBundleType(bundleType);
replace({
preventAssignment: true,
values: {
__DEV__: isProduction ? 'false' : 'true',
// ...
},
}),
jsxProd
那现在我们就主要来看一下生产模式的jsx方法,jsxProd方法的实现了
目录:packages/react/src/jsx/ReactJSXElement.js。
可以看到,这个方法接受3个参数,type,config,maybeKey,其实就是对应上一节编译结果的三个参数,元素tag,元素属性(包括子元素),元素的key。
比如说一个元素为
那么传入jsxProd的参数就是
jsxProd("div", {
name: "name",
children: "123"
}, "key");
关键逻辑源码:
export function jsxProd(type, config, maybeKey) {
// 初始化key,ref
let key = null;
let ref = null;
// 如果key存在就将它转换成字符串
if (maybeKey !== undefined) {
key = '' + maybeKey;
}
// 判断config中是否有key,有key的话覆盖一下
if (hasValidKey(config)) {
key = '' + config.key;
}
// 判断config中是否有ref,有的赋值
if (hasValidRef(config)) {
ref = config.ref;
}
}
// 初始化props
let props;
for (const propName in config) {
// 如果不是key和ref就赋值给props,因为key和ref之前已经处理过了
if (propName !== 'key' && propName !== 'ref') {
props[propName] = config[propName];
}
}
// 如果不禁用defaultProps且有defaultProps时,给props一个初始值
if (!disableDefaultPropsExceptForClasses) {
// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (const propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
}
// 将处理之后的数据传给ReactElement方法
return ReactElement(
type,
key,
ref,
undefined,
undefined,
getOwner(),
props,
undefined,
undefined,
);
}
// 判断config中是否有key
function hasValidKey(config) {
return config.key !== undefined;
}
// 判断config中是否有ref
function hasValidRef(config) {
return config.ref !== undefined;
}
我们可以看出来,jsxProd方法主要就是将入参进行了一些分类和初始化,从config中提取出来了key和ref,然后将type,key,ref,props传给了ReactElement方法
ReactElement
目录:packages/react/src/jsx/ReactJSXElement.js。
import {
REACT_ELEMENT_TYPE,
} from 'shared/ReactSymbols';
function ReactElement(
type,
key,
_ref,
self,
source,
owner,
props,
debugStack,
debugTask,
) {
let ref;
let ref;
// 是否允许ref在props中传递,如果允许就直接取props中的ref
if (enableRefAsProp) {
const refProp = props.ref;
ref = refProp !== undefined ? refProp : null;
} else {
ref = _ref;
}
let element;
element = {
$$typeof: REACT_ELEMENT_TYPE,
type,
key,
ref,
props,
_owner: owner,
};
return element;
}
可以看到,ReactElement的核心逻辑其实简单,就是将传进来的参数组成一个数据结构,这个数据结构就是ReactElement,这个数据结构有一个标识( $$typeof )为REACT_ELEMENT_TYPE,REACT_ELEMENT_TYPE是一个symbol:Symbol.for('react.transitional.element')。
所以我们可以说,所有的jsx最后都会被转换成一个ReactElement结构的js对象,而ReactElement就是后续react中的最基础的数据结构