认识jsx和babel解析jsx

大家好,我是晨晨,今天来讲讲jsx的内容


万物始于 jsx,想要深入学习 react ,就应该从 jsx 入手


我们先看一段jsx


class Index extends React.Component{
render(){
return <div style={{ marginTop:'100px' }} >
{ /* element 元素类型 */ }
<div>hello,world</div>
{ /* 文本类型 */ }
let me learn React!
</div>
}
}

babel编译jsx后是什么呢?

首先,看一下上述例子中的 jsx 模版会被babel编译成什么?


可以看到jsx会被编译为React Element的形式,但有个方法我们不了解

来看到React.createElement这个方法,它的用法是什么呢?


React.createElement(
type,
[props],
[...children]
)


  1. 第一个参数:如果是组件类型,会传入组件对应的类或函数;如果是 dom 元素类型,传入 div 或者 span 之类的字符串。
  2. 第二个参数:一个对象,在 dom 类型中为标签属性,在组件类型中为 props 。
  3. 其他参数:依次为 children,根据顺序排列。


比如刚上面的例子


<div>
<div>hello,world</div>
let me learn React!
</div>

会被babel编译为


React.createElement("div", null,
React.createElement("div", null, "hello,world"),
"let me learn React!"
)
这也是为什么老版本的React写jsx需要引入React,因为会被编译为 React.createElement形式,不引入报错

createElement的jsx转换规则

如下表格,星球的md编译好像表格不支持,将就看看


| jsx元素类型 | 转换后 | type 属性 |

| ------------------- | -----------------------------------------------------| ------------------------------- |

| element 类型 | react element 类型 | symbol react.fragment 类型 |

| fragment 类型 | react element 类型 | symbol react.fragment 类型 |

| 文本类型 | 直接字符串 | 无 |

| 数组类型 | 返回数组结构,里面元素被react.createElement 转换 | 无 |

| 组件类型 | react element 类型 | 组件类或者组件函数本身 |

| 三元运算 / 表达式 | 先执行三元运算,然后按照上述规则处理 | 看三元运算返回结果 |

| 函数执行 | 先执行函数,然后按照上述规则处理 | 看函数执行返回结果 |



最终,在调和阶段,上述 React element 对象的每一个子节点都会形成一个与之对应的 fiber 对象,然后通过 sibling、return、child 将每一个 fiber 对象联系起来。

所以,我们有必要先来看一下 React 常用的 fiber 类型,以及 element 对象和 fiber 类型的对应关系。


不同种类的 fiber Tag


React 针对不同 React element 对象会产生不同 tag (种类) 的fiber 对象。首先,来看一下 tag 与 element 的对应关系:



export const FunctionComponent = 0; // 函数组件
export const ClassComponent = 1; // 类组件
export const IndeterminateComponent = 2; // 初始化的时候不知道是函数组件还是类组件
export const HostRoot = 3; // Root Fiber 可以理解为根元素 , 通过reactDom.render()产生的根元素
export const HostPortal = 4; // 对应 ReactDOM.createPortal 产生的 Portal
export const HostComponent = 5; // dom 元素 比如 <div>
export const HostText = 6; // 文本节点
export const Fragment = 7; // 对应 <React.Fragment>
export const Mode = 8; // 对应 <React.StrictMode>
export const ContextConsumer = 9; // 对应 <Context.Consumer>
export const ContextProvider = 10; // 对应 <Context.Provider>
export const ForwardRef = 11; // 对应 React.ForwardRef
export const Profiler = 12; // 对应 <Profiler/ >
export const SuspenseComponent = 13; // 对应 <Suspense>
export const MemoComponent = 14; // 对应 React.memo 返回的组件


jsx 最终形成的 fiber 结构图


最终写的 jsx 会变成如下格式:




fiber 对应关系

  1. child: 一个由父级 fiber 指向子级 fiber 的指针。
  2. return:一个子级 fiber 指向父级 fiber 的指针。
  3. sibling: 一个 fiber 指向下一个兄弟 fiber 的指针。

温馨提示:

  1. 对于上述在 jsx 中写的 map 数组结构的子节点,外层会被加上 fragment ;
  2. map 返回数组结构,作为 fragment 的子节点。


babel解析jsx流程



JSX 语法实现来源于这两个 babel 插件:

  1. @babel/plugin-syntax-jsx : 使用这个插件,能够让 Babel 有效的解析 JSX 语法。
  2. @babel/plugin-transform-react-jsx :这个插件内部调用了 @babel/plugin-syntax-jsx,可以把 React JSX 转化成 JS 能够识别的 createElement 格式。


现在React 已经不需要引入 createElement ,这种模式来源于 Automatic Runtime,看一下是如何编译的。

业务中推荐函数式组件


function Index(){
return <div>
<h1>hello,world</h1>
<span>let us learn React</span>
</div>
}

被编译后


import { jsx as _jsx } from "react/jsx-runtime";
import { jsxs as _jsxs } from "react/jsx-runtime";
function Index() {
return _jsxs("div", {
children: [
_jsx("h1", {
children: "hello,world"
}),
_jsx("span", {
children:"let us learn React" ,
}),
],
});
}

plugin-syntax-jsx 已经向文件中提前注入了 _jsxRuntime api

但需要我们在 .babelrc 设置 runtime: automatic 。


"presets": [
["@babel/preset-react",{
"runtime": "automatic"
}]
],


如果是经典模式下,即Classic Runtime则需要引入React,看看编译后的


import React from 'react'
function Index(){
return React.createElement(
"div",
null,
React.createElement("h1", null,"hello,world"),
React.createElement("span", null, "let us learn React")
);
}

可以发现和我们上面讲的一致


简单实现babel转换


创建 test.js,写下将测试的 JSX 代码。



import React from 'react'

function TestComponent(){
return <p> hello,React </p>
}
function Index(){
return <div>
<span>模拟 babel 处理 jsx 流程。</span>
<TestComponent />
</div>
}
export default Index

因为 babel 运行在 node 环境,所以同级目录下创建 jsx.js 文件。来模拟一下编译的效果。



const fs = require('fs')
const babel = require("@babel/core")

/* 第一步:模拟读取文件内容。 */
fs.readFile('./test.js',(e,data)=>{
const code = data.toString('utf-8')
/* 第二步:转换 jsx 文件 */
const result = babel.transformSync(code, {
plugins: ["@babel/plugin-transform-react-jsx"],
});
/* 第三步:模拟重新写入内容。 */
fs.writeFile('./test.js',result.code,function(){})
})


经过处理,重新查看test.js,发现变为如下了


import React from 'react';

function TestComponent() {
return /*#__PURE__*/React.createElement("p", null, " hello,React ");
}

function Index() {
return /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("span", null, "\u6A21\u62DF babel \u5904\u7406 jsx \u6D41\u7A0B\u3002"), /*#__PURE__*/React.createElement(TestComponent, null));
}
export default Index;


就此实现成功


0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
晨晨
下载 APP