起因是guy在让我们学习preact给我们布置的一个作业,目的是让我们学习preact的h
函数后,复写h
函数,实现fineUI那一套语法,其实也并不是很难。
那天晚上我问guy到底是要重写h
函数还是写babel插件,他说都行。
于是我走了另一条路。准备直接写个babel插件把jsx直接转译成fineUI语法。
babel开发者手册是必不可少的学习资料。
如果我要求出字符串1 + 1
的值2
,有什么解决办法?
——将这个字符串通过正则搭成一棵树,再通过遍历树节点实现最后的功能。
同样,需要转译的代码对于Node.js来说就是一推字符串,我们需要将其转换成一棵树再进行操作。
还好,这件事babel已经帮我做了,这就是AST抽象语法树。
比如下一段代码:
function square(n) {
return n * n;
}
其语法树:
- FunctionDeclaration:
- id:
- Identifier:
- name: square
- params [1]
- Identifier
- name: n
- body:
- BlockStatement
- body [1]
- ReturnStatement
- argument
- BinaryExpression
- operator: *
- left
- Identifier
- name: n
- right
- Identifier
- name: n
更好地了解语法树,可以访问这个网站(国内访问很慢)。
当我们谈及“进入”一个节点,实际上是说我们在访问它们, 之所以使用这样的术语是因为有一个访问者模式(visitor)的概念。
在进入AST树后,每个节点实际上会被访问两次,向下进入节点,向上退出节点。
以Identifier
为例:
const MyVisitor = {
Identifier() {
console.log("Called!");
}
};
// 实际上等于下面
const MyVisitor = {
Identifier: {
enter() {
console.log("Called!");
}
}
};
假设我们有一个树状结构:
- FunctionDeclaration
- Identifier (id)
- Identifier (params[0])
- BlockStatement (body)
- ReturnStatement (body)
- BinaryExpression (argument)
- Identifier (left)
- Identifier (right)
当我们向下遍历这颗树的每一个分支时我们最终会走到尽头,于是我们需要往上遍历回去从而获取到下一个节点。 向下遍历这棵树我们进入每个节点,向上遍历回去时我们退出每个节点。
让我们以上面那棵树为例子走一遍这个过程。
- 进入
FunctionDeclaration
- 进入
Identifier (id)
- 走到尽头
- 退出
Identifier (id)
- 进入
Identifier (params[0])
- 走到尽头
- 退出
Identifier (params[0])
- 进入
BlockStatement (body)
- 进入
ReturnStatement (body)
- 进入
BinaryExpression (argument)
- 进入
Identifier (left)
- 走到尽头
- 退出
Identifier (left)
- 进入
Identifier (right)
- 走到尽头
- 退出
Identifier (right)
- 退出
BinaryExpression (argument)
- 进入
- 退出
ReturnStatement (body)
- 退出
BlockStatement (body)
- 进入
- 退出
FunctionDeclaration
所以当创建访问者时你实际上有两次机会来访问一个节点
babel-types的AP文档,大部分需要的API在这里都可以找到。
首先,根据AST树结构。通过babel-plugin-transform-react-jsx
转译的jsx语法会变成以下结构:
React.createElement(tag, attritubtes, child);
通过学习AST树可知React.createElement
整个方法是在CallExpression
节点被创建和访问的。
要想替换成FineUI中结构,其实只要替换CallExpression
节点。