无比打字与拼英打字

    科技2023-12-06  106

    无比打字与拼英打字

    One of Javascript’s most glaring omissions is first-class support for enums. Anyone who’s spent time in other languages knows the value of these simple structures, so it’s no surprise that one of Typescript’s few language additions is the enum. But Typescript’s implementation is quite basic — under the hood, they’re just objects, and this presents two significant pain points.

    Javascript最明显的遗漏之一就是对枚举的一流支持。 任何在其他语言上度过的人都知道这些简单结构的价值,因此Typescript少数几种语言添加之一就是枚举 ,这不足为奇。 但是Typescript的实现是非常基本的-在幕后,它们只是对象,这带来了两个重要的痛点。

    Problem 1: Iteration over Typescript enums requires converting to Array

    问题1:对Typescript枚举进行迭代需要转换为数组

    This might seem like a nitpick since ES6 gave us Object.values— but if we consider the most common use cases for enums, there’s a constant need for iteration. Converting every time we need to populate a list or dropdown is a nuisance, but there’s a hidden cost: the resulting type is no longer an enum, but a string. This quickly leads to situations where string values taken directly from an enum won’t be accepted anywhere we expect an enum.

    自从ES6为我们提供Object.values以来,这似乎像是Object.values -但是,如果我们考虑枚举的最常用用例,那么就需要不断地进行迭代。 每次我们需要填充列表或下拉列表时进行转换都是一件令人讨厌的事情,但是存在一个隐藏的代价:生成的类型不再是枚举,而是一个字符串。 这很快就会导致这样的情况:直接从枚举中获取的字符串值在我们期望枚举的任何地方都不会被接受。

    enum Bah { ... };const humbug = (bah: Bah) => {};const bahValues = Object.values(Bah);// Error: Type 'string' is not assignable to type 'Blah'humbug(bahValues[0])

    Even if we try to annotate upstream, the problem persists.

    即使我们尝试对上游进行注释,问题仍然存在。

    // Error: Type 'string' is not assignable to type 'Bah'const bahValues = Object.values<Bah>(Bah);const bahValues: Bah[] = Object.values(Bah);

    Our only option is to cast or assert, which defeats the purpose of working with strong types and creates unhelpful noise in our code.

    我们唯一的选择是强制转换或断言,这违背了使用强类型的目的,并在我们的代码中产生了无用的噪音。

    Problem 2: Typescript enums can’t be extended

    问题2:Typescript枚举无法扩展

    Enums in Python or Java are classes, which allows for custom attributes and methods directly on the enum. Some code philosophers argue this goes against the ethos of enums, which are meant to be static lists and nothing more. In my experience, however, enums don’t live in isolation from changes in the application, and they’re rarely static. Consider a few common requirements that any application might present:

    Python或Java中的枚举是类,它允许直接在枚举上使用自定义属性和方法。 一些代码哲学家认为这与枚举的精神背道而驰,枚举的本意是静态列表,仅此而已。 但是,以我的经验来看,枚举并不是孤立于应用程序中的更改,它们很少是静态的。 考虑任何应用程序可能存在的一些常见要求:

    Define a static sort order for iteration/display

    为迭代/显示定义静态排序顺序

    Custom toString for localization or business logic

    自定义toString用于本地化或业务逻辑

    Deprecating values without deleting

    弃用值而不删除 Static subsets of values

    值的静态子集

    Class-based enums make it possible colocate these features with the enum itself. Classes may have fallen out of vogue in the shift to functional-reactive styles over the last few years, but this is a situation where classes could offer the more declarative approach. How might we accomplish this in Typescript?

    基于类的枚举可以将这些功能与枚举本身并置在一起。 在过去的几年中,类可能已经不再流行于向函数式的风格转变,但是在这种情况下,类可以提供更具声明性的方法。 我们如何在Typescript中完成这项工作?

    在Typescript中编写基于类的枚举 (Writing a class-based enum in Typescript)

    Let’s start with the code, and then walk through its features.

    让我们从代码开始,然后逐步介绍其功能。

    export class Priority { static asArray: Priority[] = []; // Values static readonly CRITICAL = new Priority('CRITICAL'); static readonly HIGH = new Priority('HIGH'); static readonly MODERATE = new Priority('MODERATE'); static readonly MEDIUM = new Priority('MEDIUM', true); static readonly LOW = new Priority('LOW');' // Subsets static readonly GENERATES_WARNINGS = [ Priority.CRITICAL, Priority.HIGH, ]; static readonly ACTIVE = Priority.asArray .filter(({ deprecated }) => !deprecated); constructor( public readonly value: string, public readonly deprecated = false, ) { Priority.asArray.push(this); } valueOf() { return this.value; } toString() { return someLocalizationFunction(this.valueOf()); } get order() { return Priority.asArray.indexOf(this); }}

    First, we define the static collection asArray, as this needs to be instantiated before any values can be added. Next, we create our Priority enums. Take note that MEDIUM uses a second argument of false to designate itself as deprecated. If we look ahead to the constructor, we see that deprecated is defaulted to false for other enums, and each new Priority is getting added to the static asArray collection. After the individual values are created, we can create arbitrary subsets of values manually or by using other properties of the enum.

    首先,我们定义静态集合asArray ,因为在添加任何值之前需要实例化该静态集合。 接下来,我们创建优先级枚举。 请注意, MEDIUM使用false的第二个参数将其自身指定为deprecated 。 如果我们展望构造函数,我们会发现对于其他枚举, deprecated的默认值为false ,并且每个新的Priority都将添加到静态asArray集合中。 创建单个值后,我们可以手动创建值的任意子集,也可以使用枚举的其他属性来创建。

    Lastly, we have our accessors. Using valueOf() and toString() provides a consistent interface with ECMAScript’s objects and strings. For our order getter, we’re able to rely on the definition order of the values themselves (represented in asArray), which provides a simple mechanism to define sort order.

    最后,我们有访问器。 使用valueOf()和toString()可提供与ECMAScript的对象和字符串一致的接口。 对于我们的order ,我们能够依靠值本身的定义顺序(以asArray表示),这提供了一种定义排序顺序的简单机制。

    This gives us everything we need to start using our new enum class just like we would a Typescript enum:

    这为我们提供了开始使用新的枚举类所需的一切,就像我们要使用Typescript枚举一样:

    class ErrorMessage { constructor(public priority: Priority) {}}const criticalMessage = new ErrorMessage(Priority.CRITICAL);const allErrors = Priority.asArray.map(ErrorMessage);const warnings = Priority.GENERATES_WARNINGS.map(ErrorMessage);

    This looks great! We’ve solved for many common use cases and preserved type safety. But has this been worth all the effort?

    看起来很棒! 我们已经解决了许多常见的用例并保留了类型安全性。 但这值得所有努力吗?

    基于类的枚举有很多缺点 (Class-based enums have significant drawbacks)

    There are some issues with our implementation.

    我们的实施存在一些问题。

    As soon we start creating more enums, we’ll find ourselves attempting to factor out the common operations — but this proves to be challenging. We could make a base Enum class and move some functions like toString() and valueOf(). However, all of our static members are specific to each enum, and can’t be abstracted away. Type definitions also can’t moved to the base class, as we would need to use generics — but generics cannot be applied to static members. The end result is that even with some clever abstraction, there will still be a lot of duplicated code with each new enum.

    一旦我们开始创建更多枚举,我们将发现自己试图排除常见的操作-但这被证明是具有挑战性的。 我们可以创建一个基本的Enum类,并移动一些函数,例如toString()和valueOf() 。 但是,我们所有的静态成员都是特定于每个枚举的,因此不能抽象出来。 类型定义也不能移到基类,因为我们需要使用泛型-但是泛型不能应用于静态成员。 最终结果是,即使有了一些巧妙的抽象,每个新的枚举仍然会有很多重复的代码。

    Another problem is that these enums require instantiation. If we’re ingesting raw data from an external source — say, some JSON with a property we want to annotate:

    另一个问题是这些枚举需要实例化。 如果我们从外部来源获取原始数据(例如,一些带有我们要注释的属性的JSON):

    { error: { priority: 'CRITICAL' }}interface ExternalError { error: { priority: Priority }}

    We can’t annotate this object with our enum as-is. We would first have to transform this data to ensure that error.priority gets instantiated with our Priority enum.

    我们不能按原样使用枚举来注释此对象。 我们首先必须转换这些数据以确保error.priority被我们的Priority枚举实例化。

    const originalData = require('error.json');const transformedData: ExternalError = { error: { priority: Priority[originalData.error.priority], }};

    This creates a gap between the original data and the data used by the application. We face the reverse issue anywhere we might be sending data to an external source, requiring another transformation back into string format. This introduces additional layers in a pipeline that might otherwise have been seamless. Every time we touch data is another opportunity for bugs and corruption.

    这在原始数据和应用程序使用的数据之间造成了差距。 在可能将数据发送到外部源的任何地方,我们都面临相反的问题,需要再次转换回字符串格式。 这会在管道中引入其他层,否则这些层可能是无缝的。 每次我们接触数据时,都是发生错误和破坏的另一个机会。

    This transformation issue isn’t just isolated to file read/writes or API requests. Third-party libraries won’t accept our enums, so we may have to transform back-and-forth within individual components. It’s these handoffs that are particularly dangerous, as external dependencies might not warn us when we’ve failed to provide data in the expected format.

    这种转换问题不仅限于文件读/写或API请求。 第三方库将不接受我们的枚举,因此我们可能必须在各个组件之间来回转换。 这些切换特别危险,因为当我们未能以期望的格式提供数据时,外部依赖关系可能不会警告我们。

    So, are class-based enums worth the effort? As with most things, I think the answer is a rock-solid “it depends”.

    那么,基于类的枚举值得吗? 与大多数事情一样,我认为答案是坚如磐石的“取决于”。

    These implementations are certainly not optimal — I’m sure there’s plenty that could be improved on, leveraging some more advanced features in Typescript. Some of these improvements might properly address scalability / DRY issues. Still, the decision mostly comes down to your application’s needs.

    这些实现当然不是最佳的-我敢肯定,利用Typescript中的一些更高级的功能,还有很多可以改进的地方。 其中一些改进可能会正确解决可伸缩性/ DRY问题。 尽管如此,决定还是取决于您的应用程序需求。

    If you find that some of your enums tend to come with tightly-coupled business logic or you need a structure that flexibly supports additional properties and metadata, this may be a useful pattern. But if you just want easy iteration and don’t need any custom methods, class enums are probably overkill. I would exercise particular caution in situations where it becomes necessary to add new transformations.

    如果您发现某些枚举往往带有紧密耦合的业务逻辑,或者您需要一个可灵活支持其他属性和元数据的结构,那么这可能是一个有用的模式。 但是,如果您只想简单地迭代并且不需要任何自定义方法,则类枚举可能会过大。 在有必要添加新转换的情况下,我会特别谨慎。

    翻译自: https://medium.com/swlh/class-based-enums-in-typescript-are-they-worth-the-trouble-6b6dfa512706

    无比打字与拼英打字

    相关资源:枚举数组:打字稿枚举枚举化-源码
    Processed: 0.027, SQL: 8