用于后端开发的TypeScript

Java语言在后端开发中仍然占据主导地位。造成这种情况的原因很多:速度,安全性(当然,除非我们不看空指针),以及广阔的,经过充分测试的生态系统。但是在微服务和敏捷开发时代,其他因素变得更加重要。在某些系统中,当执行CRUD操作和数据转换的简单服务时,可能不一定要保持最佳性能并具有稳定的依赖关系的强大生态系统。此外,必须快速构建和重建许多系统,以与功能的快速迭代开发保持同步。



由于Spring Boot的无处不在,开发和部署简单的Java服务很容易。但是,由于必须测试封闭的类并且必须转换数据,因此构建器,转换器,枚举构造器和序列化器在您的代码中很多,从而为刻板的Java代码铺平了道路。这就是为什么新功能的开发通常会延迟的原因。而且,是的,代码生成是可行的,但是它不是非常灵活。



TypeScript尚未在后端开发人员中建立良好的地位。可能是因为它被称为一组声明性文件,使您可以向JavaScript添加一些类型。但是,仍然有大量的逻辑要用数十行Java来表示,并且可以用几行TypeScript来表示。

许多被称为TypeScript的典型功能的功能实际上是指JavaScript。但是TypeScript也可以被视为一种独立的语言,在语法和概念上与JavaScript相似。因此,让我们暂时离开JavaScript,单独看一下TypeScript:这是一种漂亮的语言,具有强大而灵活的类型系统,大量的语法糖,以及最终的null安全性!



我们已经在Github托管了一个带有自定义构建的Node / TypeScript Web应用程序存储库,以及一些其他说明。还有一个高级分支,其中包含洋葱架构示例 和更多非平凡的打字概念。



介绍TypeScript



让我们从基础开始:TypeScript是一种异步函数式编程语言,尽管如此,它仍支持类和接口以及公共,私有和受保护的属性。因此,程序员在使用这种语言时,在微体系结构和代码样式级别上获得了相当大的灵活性。可以动态配置TypeScript编译器,即,控制允许哪些类型的导入,如果函数需要显式的返回类型以及在编译时是否启用了零检查。



由于TypeScript可以编译为常规JavaScript,因此Node.js被用作后端运行时。在缺少类似于Spring的全面框架的情况下,典型的Web服务将使用更灵活的框架作为Web服务器(Express.js是一个很好的例子)。因此,它会变得不那么“神奇”,并且其基本设置和配置将更加明确。在这种情况下,相对复杂的服务也将需要对配置进行更多修改。另一方面,设置相对较小的应用程序并不困难,而且,几乎无需首先学习框架就可以实现。



借助Node灵活而强大的软件包管理器npm,可以轻松进行依赖关系管理。



基础



定义类时public支持访问控制修饰符protectedprivate,大多数开发人员都知道:



class Order {

    private status: OrderStatus;

    constructor(public readonly id: string, isSpecialOrder: boolean) {
        [...]
    }
}


该类现在具有Order两个属性:一个privatestatus和一个id只读public字段在打字稿,构造函数参数的关键字publicprotected或者private自动成为类的属性。



interface User {
    id?: string;
    name: string;
    t_registered: Date;
}

const user: User = { name: 'Bob', t_registered: new Date() };


请注意,由于TypeScript使用类型推断,因此即使未提供类User本身,也可以实例化User对象当使用纯数据实体时,通常会选择这种类似于结构的方法,并且不需要任何方法或内部状态。



泛型在TypeScript中的表达方式与Java中的表达方式大致相同:



class Repository<T extends StoredEntity> {
    findOneById(id: string): T {
        [...]
    }
}


强大的打字系统



TypeScript功能强大的类型系统的核心是类型推断。它还支持静态类型。但是,如果可以从上下文中推断出返回类型或参数类型,则静态类型注释是可选的。



TypeScript还允许使用联合类型,部分类型和类型交集,这为语言提供了极大的灵活性,同时避免了不必要的复杂性。在TypeScript中,您还可以使用特定的值作为类型,这在各种情况下都非常有用。



枚举,类型推断和联合类型



考虑一种常见的情况,其中订单状态需要具有类型安全的表示形式(作为枚举),但还需要用于JSON序列化的字符串表示形式。在Java中,这将是一个枚举,以及字符串值的构造函数和获取器。



在第一个示例中,TypeScript枚举允许您直接添加字符串表示形式。这给我们留下了类型安全的枚举表示形式,该表示形式会自动序列化其关联的字符串表示形式。



enum Status {
    ORDER_RECEIVED = 'order_received',
    PAYMENT_RECEIVED = 'payment_received',
    DELIVERED = 'delivered',
}

interface Order {
    status: Status;
}

const order: Order = { status: Status.ORDER_RECEIVED };


注意代码的最后一行,其中类型推断允许我们实例化与interface匹配的对象`Order`。由于不需要在我们的顺序中放入任何内部状态或逻辑,因此我们可以在没有类和没有构造函数的情况下进行操作。



的确,事实证明,通过相互共享类型和联合类型的推断,可以更轻松地解决此任务:



interface Order {
    status: 'order_received' | 'payment_received' | 'delivered';
}

const orderA: Order = { status: 'order_received' }; // 
const orderB: Order = { status: 'new' }; //  


TypeScript编译器将仅接受提供给它的字符串作为有效的订单状态(请注意,这仍然需要验证传入的JSON数据)。



基本上,此类类型表示适用于任何事物。类型很可能是字符串文字,数字和任何其他自定义类型或接口的并集。有关更多有趣的示例,请参见《TypeScript高级键入指南》



Lambda和功能参数



由于TypeScript是一种功能编程语言,因此它的核心是对匿名函数(也称为lambdas)的支持。



const evenNumbers = [ 1, 2, 3, 4, 5, 6 ].filter(i => i % 2 == 0);


上面的示例.filter()采用type函数(a: T) => boolean此函数由匿名lambda表示i => i % 2 == 0与Java不同,在Java中,函数参数必须具有显式类型,函数接口,而lambda类型也可以匿名表示:



class OrderService {
    constructor(callback: (order: Order) => void) {
        [...]
    }
}


异步编程



由于TypeScript具有所有警告,因此是JavaScript的超集,因此异步编程是该语言的关键概念。是的,您可以在此处使用lambda和回调,TypeScript具有两种基本的机制来帮助您避免回调地狱:promise和漂亮的pattern async/await一个诺言本质上是一个立即返回值,它承诺以后会返回一个特定值。



//  ,  
function fetchUserProfiles(url: string): Promise<UserProfile[]> {
    [...]
}

//     
function getActiveProfiles(): Promise<UserProfile[]> {
    return fetchUserProfiles(URL)
        .then(profiles => profiles.filter(profile => profile.active))
        .catch(error => handleError(error));
}


由于指令.then()可以以任意数量链接,因此在某些情况下,上述模式可能会导致代码混乱。通过声明一个函数asyncawait在等待promise解决时使用它,您可以以更加同步的方式编写相同的代码。同样在这种情况下,也为使用知名运算符提供了机会try/catch



//  async/await ( ,  fetchUserProfiles  )
async function getActiveProfiles(): Promise<UserProfile[]> {
    const allProfiles = await fetchUserProfiles(URL);
    return allProfiles.filter(profile => profile.active);
}

//   try/catch
async function getActiveProfilesSafe(): Promise<UserProfile[]> {
    try {
        const allProfiles = await fetchUserProfiles(URL);
        return allProfiles.filter(profile => profile.active);
    } catch (error) {
        handleError(error);
        return [];
    }
}


请注意,尽管上面的代码似乎是同步的,但它仅可见(因为此处返回了另一个promise)。



接线员和休息接线员:让您的生活更轻松



使用Java时,对象的数据操作,构造,合并和解构通常会大量产生构造型代码。必须定义类,必须生成构造函数,getter和setter,并且必须实例化对象。在测试用例中,通常需要主动诉诸于对封闭类的模拟实例的反思。



在TypeScript中,可以利用其甜美的类型安全的语法糖(即散布运算符和rest运算符)轻松地处理所有这些问题。



首先,让我们使用数组扩展运算符...拆包数组:



const a = [ 'a', 'b', 'c' ];
const b = [ 'd', 'e' ];

const result = [ ...a, ...b, 'f' ];
console.log(result);

// >> [ 'a', 'b', 'c', 'd', 'e', f' ]


当然,这很方便,但是真正的TypeScript是在您意识到可以对对象执行相同操作时开始的:



interface UserProfile {
    userId: string;
    name: string;
    email: string;
    lastUpdated?: Date;
}

interface UserProfileUpdate {
    name?: string;
    email?: string;
}

const userProfile: UserProfile = { userId: 'abc', name: 'Bob', email: 'bob@example.com' };
const update: UserProfileUpdate = { email: 'bob@example.com' };

const updated: UserProfile = { ...userProfile, ...update, lastUpdated: new Date() };

console.log(updated);

// >> { userId: 'abc', name: 'Bob', email: 'bob@example.com', lastUpdated: 2019-12-19T16:09:45.174Z}


让我们看看这里发生了什么。基本上,updated使用花括号构造函数创建对象。在此构造函数中,每个参数实际上都从左侧开始创建一个新对象。



因此,使用扩展对象userProfile;他要做的第一件事就是模仿自己。在第二步中,将扩展对象update合并到其中并重新分配给第一个对象;再次创建一个新对象。在最后一步,将字段合并并重新分配lastUpdated,然后创建新对象,并最终创建最终对象。



使用散布运算符创建不可变对象的副本是一种非常安全,快速的数据处理方法。注意:散布运算符会创建对象的浅表副本。然后将深度大于一的元素复制为链接。



扩展运算符还具有一个等效的析构函数,称为object rest



const userProfile: UserProfile = { userId: 'abc', name: 'Bob', email: 'bob@example.com' };
const { userId, ...details } = userProfile;
console.log(userId);
console.log(details);

// >> 'abc'
// >> { name: 'Bob', email: 'bob@example.com' }


现在是时候坐下来想象所有必须用Java编写的代码才能执行如上所述的操作。



结论。关于优缺点的一些知识



性能



由于TypeScript本质上是异步的,并且具有快速的运行时环境,因此在许多情况下Node / TypeScript服务可以与Java服务竞争。该堆栈特别适合I / O操作,并且可以与偶尔的短暂阻塞操作(例如调整新的配置文件图片的大小)配合使用。但是,如果服务的主要目的是在CPU上进行一些认真的计算,则Node和TypeScript可能不太适合于此。



号码类型



TypeScript中使用的类型也有很多不足之处number,即不能区分整数值和浮点值。实践表明,在许多应用中这绝对没有问题。但是,如果要为银行帐户或结帐服务编写应用程序,则最好不要使用TypeScript。



生态系统



鉴于Node.js的流行,今天有成千上万的软件包就不足为奇了。但是由于Node比Java还要年轻,因此许多软件包的生存版本不尽相同,因此某些库中的代码质量显然很差。



除其他外,值得一提的是一些使用起来非常方便的高质量库:例如,对于Web服务器依赖项注入控制器注释但是,如果该服务将严重依赖于众多且受到良好支持的第三方程序,则最好使用Python,Java或Clojure。



加速功能开发



正如我们在上面看到的,TypeScript的最重要优点之一就是用这种语言表达复杂的逻辑,概念和操作是多么容易。JSON是该语言不可或缺的一部分,如今已广泛用作数据序列化格式以进行数据传输并与面向文档的数据库一起使用,在这种情况下,诉诸TypeScript似乎是很自然的事情。设置节点服务器非常快,通常没有不必要的依赖关系。这样可以节省系统资源。这就是为什么将Node.js与TypeScript的强类型系统结合在一起如此有效地立即创建新功能的原因。



最后,TypeScript用语法糖很好地调味,因此使用它进行开发非常好且快速。



All Articles