3

TypeScript简介

TypeScript(TS)是一种强类型的编程语言,由于在编译时便会发现代码存在的问题,被前端团队大量使用。

TS使开发人员能够掌握各种组件的交互方式,并使代码重构变得更加容易;但对于需要准确定义的类型,在使用时会比较麻烦。特别是涉及数个及数十个后端接口数据的类型定义,会有大量的转换和的时间成本。通过VSCode插件实现一键生成TS声明,可以节省编程过程的开发成本。

Auto-ts-inline-types简介

为了克服TS的变量类型声明过于繁琐的缺点,节约开发成本,推荐使用VSCode插件【Auto-ts-inline-types】。它可以通过可视化窗口,模拟一个网络请求。通过接口返回的信息,或者手动修改后的信息,自动生成对应的TS声明。

此文章主要介绍插件中Json对象生成TS声明的转换原理。

转换原理

(一)生成所有的interface对象

  1. 首先对需要转换的Json对象进行遍历,遍历对象所有属性。当遇到属性key/value中的value是object的情况,则开始递归,以此循环。
  2. 当某个对象遍历后,发现所有属性的value都是基本类型,则生成typeDescription(包含HashId和typeObj)。
    (备注:HashId为引入第三方包Hash后,通过将typeObj转化为HashId而来,通过HashId保证了typeDescription的唯一性)
// 数据结构示例:
const HashId = 'cc1c310866bbc757b533d7650c9e9934b2e47bf9';
const typeObj = {
  name: string;
  version: string;
  description: string;
  main: string;
  author: string;
  license: string;
}
  1. 递归开始由内层向外层开始依次return,最后返回的typeStructure中包含rootTypeId和types(包含所有生成的typeDescription)。

img

(备注:如下图所示,在生成的types数组中包含所有的interface声明。types中每个元素的的HashId属性用来维护元数据Json对象的层级关系。但在types中,所有对象都是平级的,因为生成的interface都是平级的。在后续的转换过程中,会将HashId替换为变量名,以此生成TS声明)

image.png

  1. 如果发现某个对象的属性中,存在某些value为数组类型,则对数组进行遍历递归。

Value是数组的情况有以下5种:

image.png

对于数组的typeDescription结构体如下图所示。【arrayOfTypes】代表该数组中的所有元素类型;【isUnion】代表是上面的表格中,元素类型的第5种情况(多元素类型)。

image.png

(二)生成对应Names数组

把接口返回的信息转化为rootTypeId和types组成的对象后,就需要通过HashId和types的对应关系,找到HashId对应的Key,再将Key的首字母变为大写,生成interface中的Name。维护好一份HashId和Name的Json对应关系后,存放于数组Names中,在转换过程的最后进行替换。

image.png

  1. 首先判断由Json对象转换而来的TypeStructure对象的结构,取出RootTypeId。在types数组中找到RootTypeId对应的typeDescription,遍历typeObj。
  2. 根据typeObj的数据结构,通过arrayOfTypes来判断是否是数组。
  3. 如果是数组,则生成对应的类型。
  4. 如果是对象,则自动生成对象(包含HashId与name),放入names数组中。

image.png

(三)根据HashId映射生成TS声明

  1. 通过HashId做映射,将Names数组中的name作为interface的名称。
  2. 在types数组中,根据HashId寻找每个interface中的属性。如果存在属性的value是HashId的,则在Names中查找对应的name。
  3. 将生成的interface对象,变换为字符串的形式,包含interface以及JSDoc注释。
/**
 * @description 构造interface结构体
 */
function getInterfaceStringFromDescription(_a) {
    var name = _a.name, typeMap = _a.typeMap;
    // 构造interface内部结构
    var stringTypeMap = Object.entries(typeMap)
        .map(function (_a) {
            var key = _a[0], name = _a[1];
            return "  " + key + ": " + name + ";n";
        })
        .reduce(function (a, b) { return (a += b); }, "");

    // 注释说明
    var descriptionName = `/**n *@description ${name}n`
    // 属性类型
    var descriptionTypeMap = Object.entries(typeMap)
        .map(function (_a) {
            var key = _a[0], name = _a[1];
            return " *@param {" + name + "} " + key + "n";
        })
        .reduce(function (a, b) { return (a += b); }, "");
    // Doc注释
    var descriptionString = descriptionName + descriptionTypeMap + \' */n\'

    // 构造interface结构体
    var interfaceString = "interface " + name + " {n";
    interfaceString += stringTypeMap;
    interfaceString += "}";
    return descriptionString + interfaceString;
}

(四)生成JSDoc注释

可以在接口返回信息中,直接通过注释来添加每个字段的含义。

  1. 收集所有的用户手动添加的注释信息,生成注释信息json结构体。
  2. 在接口的返回信息中,去掉用户的注释,以使返回的文本信息可以转换为Json结构体。

image.png

  1. 在转换的结果中,替换掉已经注释含义的字段,并写入生成的JSDoc注释中。

vscode_plugin.png

/**
 * 将中文含义写入JSDoc注释
 */
const getFinalInterface = (text: string) => {
    for (let key in commentJson) {
    // commentJson是所有的字段和中文含义的对应关系
        text = text.replace(key + \'n\', key + \' \' + commentJson[key] + \'n\');
        text = text.replace(
            key + "\'" + \'n\',
            key + "\'" + \' \' + commentJson[key] + \'n\'
        );
    }
    return text;
};

问题总结

  1. 自动生成的interface对象的顺序为递归的顺序,json对象的最外层interface(RootType)会被写入生成文件的最下面。目前是手动调整顺序,将interface名为RootType的声明置于最顶层,其余字段按照从上到下的顺序排列。
if (name === \'RootObject\') {
    // 如果是根对象,则添加至数组头部
    nameMap.unshift({ id, name });
} else {
    // 如果是其他对象,则在数组后面依次添加
    nameMap.push({ id, name });
}
  1. 如果在接口返回信息中,存在多个相同key名的字段,并且用户手动写了不同的含义。目前JSDoc注释会将所有的key名都注释成相同的含义。

兰俊秋雨
5.1k 声望3.5k 粉丝

基于大前端端技术的一些探索反思总结及讨论