深入浅出React核心源码解析(1) jsx与babel的背后

2,549 阅读4分钟

一、引言

这一系列的文章我会尽我所能,用扁平化的思维,和合理顺序来帮助大家分析React到底是怎么玩的,但仍要假定读者具备最基础的开发经验与知识储备,具体的条件如下:

  • 开发过至少一个基于react的完整项目。
  • 熟悉标准js基础。
  • 了解适当的设计模式
  • 了解适当的数据结构,起码要知道什么是栈,什么是队列。

只要能满足上述的条件,那么这个系列就是适合你阅读的。

二、jsx的背后

大家可能都知道,使用create-react-app创建出的项目可以直接在js文件里书写react组件,或者jsx代码。

比如:


    const com = <div>12332</div>
    console.log(com);

像上面的代码,经过create-react-app自带的webpack编译过后,可以成功在浏览器中执行。当我们把这段代码复制出到浏览器窗口里,我们会发现控制台报错了。

很简单,因为原生js并不支持jsx语法,也就是说,标准的js运行环境中,是不允许写html标签的,js中没有这样的语法。之所以代码跑的起来,是因为经过了babel的编译。

在当下,jsx代码多数是由@babel/preset-react转译而成。

我们可以打开babel的playground查看一下背后都发生了些什么。

可以看到,实际上在浏览器中的跑的代码其实是如下一段:


"use strict";

var com = React.createElement("div", null, "123123");

可以这样试一下,js中的代码<div>123123</div>其实相等于React.createElement("div", null, "123123")

三、props

createElement这个方法,有两个必填参数,和无限剩余可选参数,第一个参数是元素的名称(字符串或者是其他类型),或者是其他类型(后面会讲),第二个参数就是大家熟知的props。

我们现在给div加个属性上去

const com = <div id="123">123123</div>

编译后

var com = React.createElement("div", {
  id: "123"
}, "123123");

可以发现,第二个参数在没有任何prop属性的情况下,是个null,可只要传入了任意一个props,就会变成一个plainObject,比如上面的{id:"123"}

至于第二个参数以后,参数的数量是零到无限的,就是该元素的直系children有多少个,后续就会有多少个参数,也是createElement方法的嵌套,我们再看下嵌套下的情况。

const com = <div id="123"><span>1</span><span>2</span></div>
var com = React.createElement("div", 
    {
        id: "123"
    }, 
    React.createElement("span", null, "1"), 
    React.createElement("span", null, "2")
);

看的出,div元素下的两个span也被转换成了createElement并且被依次排列在了第二个参数后面。

这就是babel之力,平时我们写的大部分标签语法,实际最后都转成了这个样子。createElement应该是最高频被调用的内置方法了。

四、函数组件和类组件呢?

function AComp() {
    return <div>123</div>
}

const a = <AComp/>
"use strict";

function AComp() {
  return React.createElement("div", null, "123");
}

var a = React.createElement(AComp, null);

我们可以看到<Acomp>被转换成了React.createElement(AComp, null);。这个时候我们传入的第一个参数不是字符串了,而是使用的函数组件本身。

把AComp从函数改成类组件,也会有相同的结果,具体的逻辑判断,会在react-dom中体现,这个我们后面再说。

五、组件为什么一定要大写开头

那我们现在把AComp改成小写的名字,改为acomp。

function acomp() { return <div />}

const a = <acomp/>

"use strict";

function acomp() {
  return React.createElement("div", null);
}

// 第一个参数是字符串"acomp",而非acomp函数本身
var a = React.createElement("acomp", null);

babel编译后以后可以看得出,createElement的第一个参数并不是acomp这个函数本身了,而是一个字符串,preset-react会把所有小写开头的标签,转换为字符串,它人为小写开头的元素声明,都是原生dom元素,比如div。而大写开头的就是函数组件或者类组件。

而当我们把组件写成小写调用后,就会无法调用确切想调用的组件。

六 本章总结

搞懂了jsx和Babel是咋回事后,下一节我们来分析createElement是怎么回事,他的背后到底都做了些啥子东西。