一、什么是JSX
XML + JavaScript结合的一种形式,是JavaScript的语法扩展,充分具备了JavaScript的能力。在React中本质是React.createElement()的语法糖
二、为什么要使用JSX
JSX允许前端开发者使用类HTML标签语法来创建虚拟DOM,在降低学习成本的同时,也提升了研发效率和研发体验。
三、JSX在React中是如何生效的
Babel会把JSX转译为一个名为React.createElement() 函数调用。React.createElement() 会返回一个“React Element”的JS对象,这个对象也被称为“React 元素”。示例如下:
//代码取自React官网
const element = (
<h1 className="greeting">
Hello, world!
</h1>
);
//通过babel会转化为如下:
const element = React.createElement(
'h1',
{className: 'greeting'},
'Hello, world!'
);
四、JSX是如何映射为DOM的:探究React.createElement源码
createElement源码如下,代码解析已经加入到对于的代码块前。
/**
* @param {string} type 用于标识节点的类型
* @param {object} config 组件的属性,键值对形式
* @param {object} children 组件标签间嵌套的内容
* @returns React.Element()函数调用
*/
function createElement(type, config, children){
let propName;
const props = {};
let key = null;
let ref = null;
let self = null;
let source = null;
//处理config
//config对象中存放的是元素的属性
if (config != null) {
// 给ref属性赋值 hasValidRef验证是否含有ref属性
if (hasValidRef(config)) {
ref = config.ref;
if (__DEV__) {
warnIfStringRefCannotBeAutoConverted(config);
}
}
// 将key字符串化 hasValidKey验证是否含有key属性
if (hasValidKey(config)) {
if (__DEV__) {
checkKeyStringCoercion(config.key);
}
key = '' + config.key;
}
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
//遍历config,将除了上面这四个属性以外的属性添加到props中
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
// childrenLength是当前元素子元素的个数,arguments的长度减去2是减去的type和config占用的长度
const childrenLength = arguments.length - 2;
// 如果除去type和config,只剩下一个参数,通常情况下是文本节点,则直接赋值给props.children
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
// 如果有多个子元素,则将子元素推入childArray 并赋值给props.children
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}
// 处理defaultProps
// 如果type中存在defaultProps,遍历defaultProps,将这些属性在props中值为undefined的重新赋值
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
if (__DEV__) {
// 判断元素是否存在key和ref属性
if (key || ref) {
// 校验type是不是一个函数,如果是函数,就表示当前元素是组件。如果不是函数直接返回元素类型字符串
// displayName 用于报错过程中显示哪个组件报错了,如果开发者定义了displayName就显示开发者定义的,否则显示组件名称,如果组件没有名称显示'Unknown'
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
// 如果key属性存在,给props对象添加key属性 并指定使用props.key时报错
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
// 如果ref属性存在,给props对象添加ref属性 并指定使用props.ref时报错
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
}
// 返回ReactElement的调用
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
阅读完上面的代码后,可以发现createElement函数是一个数据处理函数,处理好参数后发起React.Element函数的调用。那么React.Element又是何方神圣?请继续阅读接下来关于React.Element的源码解析。
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// 定义元素类型
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
// 记录创建元素的组件
_owner: owner,
};
// 针对DEV环境下的一些处理
if (__DEV__) {
element._store = {};
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false,
});
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self,
});
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source,
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}
return element;
};
阅读完ReactElement的源码后发现,这个函数的代码非常简单,仅仅是将参数组装成一个ReactElement对象返回给了createElement。这个ReactElement对象其实就是我们常说的虚拟DOM。
那么这个虚拟DOM又是如何映射成为真实DOM的呢?在React18版本之前都是通过ReactDOM.render()方法实现的,在React 18中,render 函数已被 createRoot 函数所取代。(渲染过程会在其他篇章做详细解析,此处不再赘述)
五、总结
开发者编写JSX代码通过babel编译为React.createElement()函数的调用,此函数通过调用ReactElement函数返回一个ReactElement对象(又称“虚拟DOM”),再通过ReactDOM.render / ReactDOM.createRoot函数将虚拟DOM渲染成为真实DOM。最后一图带你看懂JSX是如何变成真实DOM的。