最近在B站上看到React高级进阶教程视频,看完后觉得非常不错,故以整理成文章,以便后续翻阅
首先,提出关于JSX的三个"大问题",相信读完这篇文章的最后,回头再解答这三个问题,易如反掌。
- jsx的本质是什么,它和JS之间到底是什么关系?
- 为什么要用JSX?不用会有什么后果?
- JSX背后的功能模块是什么?这个功能模块都做了哪些事情?
首先看看React官网给出的JSX定义:
JSX是JavaScript的语法扩展,它和模版语法很近,但它充分具备JavaScript的能力。
FaceBook公司给JSX的定位是JavaScript的扩展,这就直接决定了浏览器并不会像天然支持JavaScript一样支持JSX。
React官网其实早已经给过线索:
JSX会被编译为React.creatlement(), React.creatElement()将返回一个叫做“React Element”的JS对象。
我们暂且不纠结React.creatlement()这个API,先看看上面提到的编译是怎么回事?
Babel官网给出的定义是:
Babel是一个工具链,主要用于将ECMAScript2015+版本的代码转换为向后兼容的JavaScript语法,以便能够运行当前和旧版本的浏览器或其他环境中。
举个🌰 下面是一段模版字符串的代码
var name = "GuyFireri";
var place = "Flavortown"
`Hello ${name}, ready for ${place}?`;
Babel就可以将上段代码转化为大部分低版本浏览器都支持es5格式的代码:
var name = "GuyFireri";
var place = "Flavortown"
"Hello ".concat(name, ",ready for").concat(place,"?")
那么Babel具体会将JSX转换成什么样呢? 我们可以借助Babel官网提供的PlayGround来一探究竟
可以看到,所有的JSX的标签都被Babel转换成了React.creatElement的函数调用,这就意味着我们写的JSX就是React.creatElement的函数调用。虽然看起像HTML,但也只是看起来像而已。
所以,JSX的本质是React.creatElement这个JavaScript的调用语法糖。
这也就完美的呼应上了React官网给出的JSX充分具备JavaScript的能力。
下面我们来看看React.CreatElement源码剖析
/** * React的创建元素方法 */
export function createElement(type, config, children) {
// propName变量用于储存后面需要用到的元素属性
let propName:
// props变量用于存储元素属性的键值对集合
const props = {};
// key,ref,self,source均为React元素的属性,此处不必深究
let key = null;
let ref = null;
let self = null;
let source = null;
// config 对象中存储的是元素的属性
if(config != null){
// 进来之后做的第一件事,是一次对ref,key,self和source属性赋值 、
if(hasValidRef(config)) ref = config.ref; // 此处将key值字符串化
if(hasValidKey(config)) key = "" + config.key;
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
for(propName in config){
if(
// 筛选出可以提进props对象里的属性
hasOwnProperty.call(config,propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)){
props[propName] = config[propName];
}
}
}
// childrenLength指的是当前元素的子元素的个数,减去的2是type和config两个参数占用的长度
const childrenLength = arguments.length - 2; // 如果抛去type和config。就剩下一个参数,一般意味着文本节点出现了
if(childrenLength === 1) {
// 直接把这个参数的值赋给props.children
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
props.children = childArray;
}
// 处理defaultProps
if(type && type.defaultProps){
const defaultProps = type.defauotProps;
for(propName in defaultProps){
if(props[propName] === undefined){
props[propName] = defaultProps[propName];
}
}
}
// 最后返回一个屌用ReactElement执行方法,并传入刚才处理过的函数
return ReactElement(type, key, ref, self, source, ReactCurrentOwner,current, props) }
通过逐行注释代码的方式,相信我们对createElement所做的事情已经了然于心,这里就不过多赘述,下面放一张图来总结该函数的具体功能
通过分析源码,我们发现React.createElement最终返回的是ReactElement的函数执行结果,接下来让我们顺势看看ReactElement的源码
const ReactElement = function(type, key, ref, self, source, owner, props){
const element = {
// REACT\_ELEMENT\_TYPE是一个常量,用来标识该对象是一个ReactElement
$$typeof: REACT\_ELEMENT\_TYPE,
// 内置属性赋值
type: type,
key: key,
ref: ref,
props: props,
// 记录创造该元素的组件
_owner: owner
};
if(__DEV__){
// 这里针对dev环境的处理, 直接忽略
}
return element;
}
看到这里,可能有同学会感觉到惊讶,ReactElement函数竟然如此简单,仅仅做了一层数据格式化,没错,就是这么简单。不过多赘述,直接上图。
那么ReactElement最终返回element对象,就是我们经常听到的虚拟DOM
有兴趣的可以通过下述的方式,将element对象打印出来进行观察
const AppJSX = (
<div className="App">
<h1 className="title"> I am the title </h1>
<p className="content"> I am the content </p>
</div>
)
打印结果大致如下:
从虚拟dom到真实dom的工作由ReactDOM.render方法完成,ReactDOM.render我们后续再做详细讲解。
最后用一张图总结JSX的一生
Say GoodBye ~~