首先咱们来看看上面的代码
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 --savenpm 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"
参考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").defaultfunction 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);