核心内容:JSX的节点类型、生成JS代码
1. DEMO
1.1 Code
<div className="container">
<span>{count}</span>
</div>
1.2 AST树
JSXElement // 根节点
├── openingElement: JSXOpeningElement // 开始标签
│ ├── name: JSXIdentifier(name: "div") // div
│ └── attributes: [ // 属性数组
│ JSXAttribute // className 属性
│ ├── name: JSXIdentifier(name: "className")
│ └── value: StringLiteral(value: "container")
│ ]
├── children: [ // 子节点数组
│ JSXElement // span 元素
│ ├── openingElement: JSXOpeningElement
│ │ └── name: JSXIdentifier(name: "span")
│ ├── children: [
│ │ JSXExpressionContainer // 表达式容器
│ │ └── expression: Identifier(name: "count")
│ ]
│ └── closingElement: JSXClosingElement
│ └── name: JSXIdentifier(name: "span")
]
└── closingElement: JSXClosingElement // 结束标签
└── name: JSXIdentifier(name: "div")
1.3 JSON
{
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": { "type": "JSXIdentifier", "name": "div"},
"attributes": [
{
"type": "JSXAttribute",
"name": { "type": "JSXIdentifier", "name": "className"},
"value": { "type": "StringLiteral", "value": "container" }
}
]
},
"children": [
{
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": { "type": "JSXIdentifier", "name": "span" }
},
"children": [
{
"type": "JSXExpressionContainer",
"expression": { "type": "Identifier", "name": "count" }
}
],
"closingElement": {
"type": "JSXClosingElement",
"name": { "type": "JSXIdentifier", "name": "span"
}
}
}
],
"closingElement": {
"type": "JSXClosingElement",
"name": { "type": "JSXIdentifier", "name": "div" }
}
}
2. JSX AST节点
2.1 AST节点的关系
JSXElement // JSX元素
├── JSXOpeningElement // 开始标签
│ ├── name: JSXIdentifier | JSXMemberExpression // 标签名
│ ├── attributes: Array<JSXAttribute | JSXSpreadAttribute> // 属性列表
│ └── selfClosing: boolean // 是否自闭合
├── JSXClosingElement // 结束标签(可选)
│ └── name: JSXIdentifier | JSXMemberExpression // 标签名
└── children: Array<JSXChild> // 子节点列表
├── JSXText // 文本节点
│ ├── value: string // 文本内容
│ └── raw: string // 原始文本
├── JSXExpressionContainer // 表达式容器
│ └── expression: Expression // 表达式内容
├── JSXFragment // JSX片段
│ ├── openingFragment // 开始片段
│ ├── closingFragment // 结束片段
│ └── children: Array<JSXChild> // 子节点列表
└── JSXElement // 嵌套的JSX元素
JSXAttribute // JSX属性
├── name: JSXIdentifier // 属性名
└── value: // 属性值
├── StringLiteral // 字符串字面量
├── JSXExpressionContainer // 表达式容器
└── null // 无值
JSXSpreadAttribute // 展开属性
└── argument: Expression // 展开的表达式
JSXMemberExpression
├── object: JSXMemberExpression // Menu.Item
│ ├── object: JSXIdentifier // Menu
│ └── property: JSXIdentifier // Item
└── property: JSXIdentifier // SubMenu
2.2 JSX表达式节点
// <div className="container">Hello</div>
interface JSXElement {
type: "JSXElement";
openingElement: JSXOpeningElement;
closingElement: JSXClosingElement | null;
children: Array<JSXChild>;
}
// <div className="container" ...>
interface JSXOpeningElement {
type: "JSXOpeningElement";
name: JSXIdentifier | JSXMemberExpression;
attributes: Array<JSXAttribute | JSXSpreadAttribute>;
selfClosing: boolean;
}
// </div>
interface JSXClosingElement {
type: "JSXClosingElement";
name: JSXIdentifier | JSXMemberExpression;
}
2.3 JSX标识符和属性节点
interface JSXIdentifier {
type: "JSXIdentifier";
name: string;
}
interface JSXAttribute {
type: "JSXAttribute";
name: JSXIdentifier;
value: StringLiteral | JSXExpressionContainer | null;
}
// <button {...props}>Click</button>
interface JSXSpreadAttribute {
type: "JSXSpreadAttribute";
argument: Expression;
}
// <Menu.Item>Click me</Menu.Item>
interface JSXMemberExpression {
type: "JSXMemberExpression";
object: JSXIdentifier | JSXMemberExpression; // 对象部分
property: JSXIdentifier; // 属性部分
}
2.4 JSX文本和表达式容器
interface JSXText {
type: "JSXText";
value: string;
raw: string;
}
// <div>{count + 1}</div>
interface JSXExpressionContainer {
type: "JSXExpressionContainer";
expression: Expression;
}
// <> <span>Fragment</span> </>
interface JSXFragment {
type: "JSXFragment";
openingFragment: JSXOpeningFragment;
closingFragment: JSXClosingFragment;
children: Array<JSXChild>;
}
3. 逐个节点说明
3.1 JSXElement JSXOpeningElement JSXClosingElement JSXMemberExpression
组成一个我们常见的元素
<div {...props} className="container"></div>
JSXElement // 根节点
├── openingElement: JSXOpeningElement // 开始标签
│ ├── name: JSXIdentifier(name: "div") // div
│ └── attributes: [ // 属性数组
│ JSXSpreadAttribute
│ └── argument: SpreadElement
│ └── argument: Identifier ("props")
│ JSXAttribute // className 属性
│ ├── name: JSXIdentifier(name: "className")
│ └── value: StringLiteral(value: "container")
│ ]
└── closingElement: JSXClosingElement // 结束标签
└── name: JSXIdentifier(name: "div")
{
"type": "JSXElement"
"openingElement": {
"type": "JSXOpeningElement",
"name": { "type": "JSXIdentifier", "name": "div"}
"attributes": [
{
"type": "JSXSpreadAttribute",
"argument": {
"type": "SpreadElement",
"argument": { "type": "Identifier", "name": "props" }
}
},
{
"type" : "JSXAttribute",
"name" : { "type": "JSXIdentifier", "name" : "className" },
"value": { "type": "StringLiteral", "value": "container" }
}
]
},
"closingElement": {
"type": "JSXClosingElement",
"name": { "type": "JSXIdentifier", name: "div"}
}
}
3.2 JSXMemberExpression
<Menu.Item>Click me</Menu.Item>
{
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": {
"type": "JSXMemberExpression",
"object": { "type": "JSXIdentifier", "name": "Menu" },
"property": { "type": "JSXIdentifier", "name": "Item" }
},
"attributes": [],
"selfClosing": false
},
"children": [
{
"type": "JSXText", "value": "Click me", "raw": "Click me"
}
]
}
3.3 JSXExpressionContainer
<div>{count + 1}</div>
{
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": { "type": "JSXIdentifier", "name": "div" },
"attributes": [],
"selfClosing": false
},
"children": [
{
"type": "JSXExpressionContainer",
"expression": {
"type": "BinaryExpression",
"operator": "+",
"left": { "type": "Identifier", "name": "count" },
"right": { "type": "NumericLiteral", "value": 1 }
}
}
]
}
4. 一个完整的function组件的AST
function TestEle () {
return <div className="container"></div>
}
Program
└── FunctionDeclaration (TestEle)
├── id: Identifier (name: "TestEle")
├── params: []
└── body: BlockStatement
└── ReturnStatement
└── argument: JSXElement (div)
├── openingElement: JSXOpeningElement
│ ├── name: JSXIdentifier (name: "div")
│ └── attributes: [ │ └── JSXAttribute │ ├── name: JSXIdentifier (name: "className") │ └── value: StringLiteral (value: "container") │ ]
├── children: []
└── closingElement: JSXClosingElement
└── name: JSXIdentifier (name: "div")
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": { "type": "Identifier", "name": "TestEle" },
"params": [],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ReturnStatement",
"argument": {
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": { "type": "JSXIdentifier", "name": "div" },
"attributes": [
{
"type": "JSXAttribute",
"name": { "type": "JSXIdentifier", "name": "className" },
"value": { "type": "StringLiteral", "value": "container" }
}
],
"selfClosing": false
},
"closingElement": {
"type": "JSXClosingElement",
"name": { "type": "JSXIdentifier", "name": "div" }
},
"children": []
}
}
]
}
}
]
}
function TestEle() {
return React.createElement(
"div",
{
"className": "container"
}
);
}
5. 根据JSX AST生成Javascript代码
- JSX 需要转换为 React.createElement() 调用
- 属性需要转换为对象形式
- 展开运算符需要在属性对象中正确处理
- 字符串字面量需要保持原样
function generateCode(node) {
// 处理 JSXElement 节点
function generateJSXElement(node) {
const elementName = generateJSXIdentifier(node.openingElement.name);
const props = generateProps(node.openingElement.attributes);
const children = node.children.map(generateJSXChild).filter(Boolean);
return `React.createElement(
${elementName},
${props},
${children.join(',')}
)`;
}
// 处理属性
function generateProps(attributes) {
if (attributes.length === 0) {
return 'null';
}
const props = [];
const spreads = [];
for (const attr of attributes) {
if (attr.type === 'JSXSpreadAttribute') {
spreads.push(generateSpreadAttribute(attr));
} else if (attr.type === 'JSXAttribute') {
props.push(generateAttribute(attr));
}
}
if (spreads.length === 0) {
return `{${props.join(',')}}`;
}
// 如果有展开运算符,使用 Object.assign
const objectsToMerge = [];
if (props.length > 0) {
objectsToMerge.push(`{${props.join(',')}}`);
}
objectsToMerge.push(...spreads);
return `Object.assign({}, ${objectsToMerge.join(', ')})`;
}
// 处理展开属性
function generateSpreadAttribute(node) {
return generateIdentifier(node.argument.argument);
}
// 处理普通属性
function generateAttribute(node) {
const name = node.name.name;
const value = generateAttributeValue(node.value);
return `"${name}": ${value}`;
}
// 处理属性值
function generateAttributeValue(node) {
if (node.type === 'StringLiteral') {
return `"${node.value}"`;
}
// 可以根据需要添加其他类型的处理
return node.value;
}
// 处理标识符
function generateJSXIdentifier(node) {
if (node.type === 'JSXIdentifier') {
// 内置 HTML 元素使用字符串
if (/^[a-z]/.test(node.name)) {
return `"${node.name}"`;
}
// 自定义组件使用变量引用
return node.name;
}
}
function generateIdentifier(node) {
return node.name;
}
// 处理子节点
function generateJSXChild(node) {
// 可以根据需要添加其他类型的子节点处理
return null;
}
return generateJSXElement(node);
}
// 输入的 AST 节点
const ast = {
"type": "JSXElement",
"openingElement": {
"type": "JSXOpeningElement",
"name": {
"type": "JSXIdentifier",
"name": "div"
},
"attributes": [
{
"type": "JSXSpreadAttribute",
"argument": {
"type": "SpreadElement",
"argument": {
"type": "Identifier",
"name": "props"
}
}
},
{
"type": "JSXAttribute",
"name": {
"type": "JSXIdentifier",
"name": "className"
},
"value": {
"type": "StringLiteral",
"value": "container"
}
}
],
"selfClosing": false
},
"closingElement": {
"type": "JSXClosingElement",
"name": {
"type": "JSXIdentifier",
"name": "div"
}
},
"children": []
};
// 生成代码
const code = generateCode(ast);
console.log(code);
输出结果
React.createElement(
"div",
Object.assign({}, props, {"className": "container"}),
)