@babel/types深度应用

11,279 阅读2分钟

前文说过,types已经集成到@babel/core里,当然也可以单独安装:

npm i -D @babel/types

概述

@babel/types的用途主要有3种:

类型集合

当你在ts中使用babel时,types可以为你提供全部节点对应的类型。

let n1: types.Identifier
let n2: types.ExpressionStatement

类型判断

每一种节点类型,都有对应的类型判断方法:

if(types.isIdentifier(n1)) {
    // ...
}

if(types.isExpressionStatement(n2)) {
    // ...
}

每个类型判断方法,都实现了类型保护,顺道聊一下

TypeScript 类型保护。

先定义一个泛型的类型保护函数:

type IsType<T> = (target: any) => target is T

然后实现一下string类型保护:

const isString: IsType<string> = (target): target is string => {
    return typeof target === 'string'
}

if(isString(abc)) {
    // 当此处调用abc时,vscode会按照string类型给予足够提示
    abc.slice
}

当然,原生值类型的类型判断比较简单,上面的代码和

if(typeof abc === 'string') {
    // ...
}

效果一样。下面对自定义复杂类型做一下类型保护:

type TComplex = {
    name: string
    age: number
}

const isComplex: IsType<TComplex> = (target): target is TComplex => {
    return (
        target
        && typeof target.name === 'string'
        && typeof target.age === 'number'
        && !isNaN(target.age)
    )
}

至于isComplex内部如何实现,完全看场景和需求。这一段的关键是:通过类型保护实现的判断,在判断的内部,完全按照该变量类型结构进行提示,开发体验非常棒。

创建节点

每一种节点类型,都有对应的创建方法:

const id = types.identifier('abc')
const str = types.stringLiteral('Hello World')
const num = types.numericLiteral(10e3)

实践

先写个log方法

import * as types from '@babel/types'
import gen from '@babel/generator'

const log = (node: types.Node) => {
    console.log(gen(node).code)
}

创建值类型

log(types.stringLiteral('string'))
log(types.numericLiteral(10e4))
log(types.booleanLiteral(0.5 > Math.random()))
log(types.regExpLiteral('\\.jsx?$', 'g'))
% ts-node gen.ts
"string"
100000
false
/\.jsx?$/g

创建Array

log(
    types.arrayExpression([
        types.stringLiteral('string'),
        types.numericLiteral(10e4),
        types.booleanLiteral(0.5 > Math.random()),
        types.regExpLiteral('\\.jsx?$', 'g')
    ])
)
% ts-node gen.ts
["string", 100000, false, /\.jsx?$/g]

创建Object

log(
    types.objectExpression([
        types.objectProperty(
            types.identifier('a'),
            types.nullLiteral()
        ),
        types.objectProperty(
            // 字符串类型 key
            types.stringLiteral('*'),
            types.arrayExpression([]),
        ),
        types.objectProperty(
            types.identifier('id'),
            types.identifier('id'),
            false,
            // shorthand 对 { id: id } 简写为 { id }
            true
        ),
        types.objectProperty(
            types.memberExpression(
                types.identifier('props'),
                types.identifier('class')
            ),
            types.booleanLiteral(true),
            // 计算值 key
            true
        )
    ])
)
% ts-node gen.ts
{
  a: null,
  "*": [],
  id,
  [props.class]: true
}

创建具名 function

log(
    types.functionDeclaration(
        types.identifier('foo'),
        [types.identifier('arg1')],
        types.blockStatement([
            types.expressionStatement(
                types.callExpression(
                    types.identifier('console.log'),
                    [types.identifier('arg1')]
                )
            )
        ])
    )
)
% ts-node gen.ts
function foo(arg1) {
  console.log(arg1);
}

创建箭头函数

log(
    // 无实体代码块
    types.arrowFunctionExpression(
        [types.identifier('arg1')],
        types.callExpression(
            types.identifier('console.log'),
            [types.identifier('arg1')]
        )
    )
)
% ts-node gen.ts
arg1 => console.log(arg1)
log(
    // 有实体代码块
    types.arrowFunctionExpression(
        [types.identifier('arg1')],
        types.blockStatement([
            types.expressionStatement(
                types.callExpression(
                    types.identifier('console.log'),
                    [types.identifier('arg1')]
                )
            )
        ])
    )
)
% ts-node gen.ts
arg1 => {
  console.log(arg1);
}

JSX绑定值

log(
    types.jsxExpressionContainer(types.identifier('props.name'))
)
% ts-node gen.ts
{props.name}

JSX节点

log(
    types.jsxElement(
        types.jsxOpeningElement(types.jsxIdentifier('Text'), []),
        types.jsxClosingElement(types.jsxIdentifier('Text')),
        [types.jsxExpressionContainer(types.identifier('props.name'))]
    )
)
% ts-node gen.ts
<Text>{props.name}</Text>

JSXFragment

log(
    types.jsxFragment(
        types.jsxOpeningFragment(),
        types.jsxClosingFragment(),
        [
            types.jsxElement(
                types.jsxOpeningElement(types.jsxIdentifier('Text'), []),
                types.jsxClosingElement(types.jsxIdentifier('Text')),
                [types.jsxExpressionContainer(types.identifier('props.name'))]
            ),
            types.jsxElement(
                types.jsxOpeningElement(types.jsxIdentifier('Text'), []),
                types.jsxClosingElement(types.jsxIdentifier('Text')),
                [types.jsxExpressionContainer(types.identifier('props.age'))]
            )
        ]
    )
)
% ts-node gen.ts
<><Text>{props.name}</Text><Text>{props.age}</Text></>

综合应用:生成React函数式组件

log(
    types.program([
        types.importDeclaration(
            [types.importDefaultSpecifier(types.identifier('React'))],
            types.stringLiteral('react')
        ),
        types.exportDefaultDeclaration(
            types.arrowFunctionExpression(
                [types.identifier('props')],
                types.jsxElement(
                    types.jsxOpeningElement(types.jsxIdentifier('Component'), [
                        types.jsxAttribute(
                            types.jsxIdentifier('onClick'),
                            types.jSXExpressionContainer(
                                types.identifier('handleClick')
                            )
                        )
                    ]),
                    types.jsxClosingElement(types.jsxIdentifier('Component')),
                    [
                        types.jsxElement(
                            types.jsxOpeningElement(types.jsxIdentifier('Image'), [
                                types.jsxAttribute(
                                    types.jsxIdentifier('src'),
                                    types.stringLiteral('https://image1.suning.cn/uimg/cms/img/159642507148437980.png')
                                )
                            ]),
                            types.jsxClosingElement(types.jsxIdentifier('Image')),
                            [],
                            true
                        )
                    ],
                    false
                )
            )
        )
    ])
)
% ts-node gen.ts
import React from "react";
export default (props => <Component onClick={handleClick}><Image src="https://image1.suning.cn/uimg/cms/img/159642507148437980.png"></Image></Component>);

主要是经验总结的流水账,多用多试就对了。

以上。