首先我們來看看下面的程式碼
import "react" from "react";
const element = (<div>
<div>
<span>1</span>
<span>2</span>
<span>3</span>
</div>
<div>1</div>
<div>2</div>
</div>)
console.log(element)
複製程式碼
問題來了,
element
是如何輸出上圖所示的結構的?
環境配置
安裝react
和babel
npm i react react-dom --save
npm i @babel/core @babel/preset-env @babel/plugin-transform-react-jsx --save-dev
複製程式碼
配置babel
{
test: /\.(js|jsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
{
"presets": [
"@babel/preset-env"
],
"plugins": [
"@babel/plugin-transform-react-jsx"
]
},
cacheDirectory: true,
}
}
複製程式碼
@babel/plugin-transform-react-jsx
做了什麼?
遇到
<div>123</div>
執行
React.createElement("div", "123");
遇到
<div>
<div>1</div>
<div>2</div>
<div>3</div>
</div>
執行
React.createElement("div",
React.createElement("div", "1"),
React.createElement("div", "2"),
React.createElement("div", "3")
)
// 也就是說,用react開發的時候只要你用到了jsx語法,那麼不管你有沒有用到React都必須import react from "react"
複製程式碼
寫個函式來模擬它的執行過程
為了便於理解 我們把
<div>
<div>
<span>1</span>
<span>2</span>
<span>3</span>
</div>
<div>1</div>
<div>2</div>
</div>
當做一棵樹
let element = {
type:"div",
children:[{
type:"div",
children:[{
type:"span",
children:"1"
}, {
type:"span",
children:"2"
}, {
type:"span",
children:"3"
}]
}, {
type:"div",
children:1
}, {
type:"div",
children:2
}]
}
寫一個函式對這顆樹進行深度遍歷
function jsxTransformNode(element, callback){
let children = [];
if (Array.isArray(element.children)) {
children = element.children.map(child => jsxTransformNode(child, callback))
} else {
children = [element.chidren]
}
return callback(element.type, ...children);
}
let nodes = jsxTransformNode(child, function ReactCreateElement(type, ...children){
return {
tag: type,
children
}
})
複製程式碼
@babel/plugin-transform-react-jsx
的原理
對babel
不熟的話可以先看這邊文章從零開始編寫一個babel外掛
它其實就是將
<div className="name" age="12">
<div>1</div>
<div>2</div>
<div>3</div>
</div>
轉化為
React.createElement(
"div",
{},
React.createElement("div", {}, ...chidren),
React.createElement("div", {}, ...chidren),
React.createElement("div", {}, ...chidren)
)
程式碼塊
複製程式碼
廢話不多說直接上程式碼,下面是我寫的一個簡單的babel-plugin
來對jsx
語法進行解析
var generator = require("@babel/generator").default
function buildAttrsCall (attribs, t){
let properties = [];
attribs.forEach(attr => {
let name = attr.name.name;
let value = attr.value;
properties.push(t.objectProperty(t.stringLiteral(name), value))
});
return t.ObjectExpression(properties);
}
const createVisitor = (t) => {
const visitor = {};
visitor.JSXElement = {
// 為什麼是exit,因為jsx是DFS而不是BFS;
exit(path, file){
let openingPath = path.get("openingElement");
let children = t.react.buildChildren(openingPath.parent);
let tagNode = t.identifier(openingPath.node.name.name);
// 建立React.createElement
let createElement = t.memberExpression(t.identifier("React"),t.identifier("createElement"));
// 建立屬性
let attribs = buildAttrsCall(openingPath.node.attributes, t);
// 建立React.createElement(tag, attrs, ...chidren)表示式
let callExpr = t.callExpression(createElement, [tagNode, attribs, ...children]);
path.replaceWith(t.inherits(callExpr, path.node));
}
}
return {
visitor,
// 配置jsx解析器
inherits:() => {
return {
manipulateOptions(opts, parserOpts) {
parserOpts.plugins.push("jsx");
}
};
}
}
}
module.exports = function(babel){
const t = babel.types;
return createVisitor(t);
}
複製程式碼
- 建立tagNode變數
- 建立React.createElement表示式
- 建立attribs物件
- 建立React.createElement("div", {}, ...children)表示式
- 最後替換node
效果如下
原始碼如下
const a = <div className="name" age="12">
<div>1</div>
<div>2</div>
<div>3</div>
</div>;
複製程式碼
編譯之後
var a = React.createElement(div, {
"className": "name",
"age": "12"
}, React.createElement(div, {}, "1"), React.createElement(div, {}, "2"), React.createElement(div, {}, "3"));
console.log(a);
複製程式碼