【问题标题】:TypeScript - property decorator as type guardTypeScript - 属性装饰器作为类型保护
【发布时间】:2021-05-17 21:24:22
【问题描述】:

我想知道是否可以在 TypeScript 中使用 property decorator 作为 type guard 以确保该属性是不 null |未定义

假设有以下示例。

class MyClass {
   @isRequired()
   public myProperty: string | undefined;
} 

请注意,由于严格的编译器设置 (strictNullChecks),必须编写string | undefined。因此,仅使用 string 作为类型是不可能的。我知道 non-null-assertion 可以这样使用:public myProperty!: string。但这实际上是装饰者应该注意的。

最后,装饰器基本上检查属性是否设置在特定时间点,如果没有则抛出错误。这种检查显然不是在构建时进行的,否则就不需要这种方法。它在不久之后执行 - 例如在 Angular 等框架的 lifecylce 钩子 中。我知道类型检查在 constructor 本身内是不正确的。不过我可以接受。如果检查成功,则属性的类型应缩小到string,因此您可以安全地使用它,例如this.property.split(" ").

我想知道这样的事情在理论上是否可行?提前致谢。

【问题讨论】:

  • 我实际上知道这篇文章 - 谢谢,但我不认为它缩小了类型。这就是我想解决的问题。

标签: typescript decorator typescript-decorator angular-decorator


【解决方案1】:

不,目前这在 TypeScript 中是不可能的;装饰器不会改变他们装饰的东西的类型。在 microsoft/TypeScript#4881 中有一个长期的建议允许这样做,但该问题对于此类功能的具体工作方式存在重大分歧。

更重要的是,在decorators proposal 达到the TC39 process 的第 3 阶段以介绍 JavaScript 之前,不太可能对 TypeScript 中的装饰器的工作方式进行任何更改。一般来说,TypeScript 仅在它们是相对稳定的候选者时才尝试支持潜在的 JavaScript 功能。装饰器是earlier features of TypeScript 之一,但这么早采用特性有缺点;在某些时候,装饰器可能会以与 TypeScript 最初预期的方式截然不同的形式将其放入 JavaScript,然后 TypeScript 将不得不做出重大更改。现在你必须启用--experimentalDecorators compiler option 才能使用它们。看起来装饰者已经在第 2 阶段停滞了很长时间(至少……三、四年?)。所以现在,我预计不会有任何变化。


那么,如果您需要此功能,您可以做些什么呢?好吧,您总是可以退回到非装饰器世界,而是使用修改传递给它们的类构造函数的函数。例如(不确定这是否是一个好的实现):

function isRequired<
  C extends new (...args: any[]) => any, 
  K extends keyof InstanceType<C>
>(
    ctor: C,
    prop: K
) {
    return class extends ctor {
        constructor(...args: any[]) {
            super(...args);
        }
        init() {
            if (this[prop as keyof this] == null) throw new Error(prop + " is nullish!!");
        }
    } as Pick<C, keyof C> & (new (...args: ConstructorParameters<C>) =>
        ({ [P in keyof InstanceType<C>]:
            P extends K ? NonNullable<InstanceType<C>[P]> : InstanceType<C>[P]
        } & { init(): void })
    );
}

如果您在类构造函数和属性名称上调用 isRequired(),则生成的构造函数将在该属性上生成具有不可空值的实例,至少在您对实例调用 init() 之后(作为一些示例生命周期钩子):

const MyClass = isRequired(class MyClass {
    public myProperty: string | undefined;
}, "myProperty");
type MyClass = InstanceType<typeof MyClass>;

const myClass = new MyClass();
myClass.myProperty = "okay";
myClass.init();
console.log(myClass.myProperty.toUpperCase()); // OKAY

const badMyClass = new MyClass();
badMyClass.init(); // myProperty is nullish!!
console.log(badMyClass.myProperty.toUpperCase()); // NEVER GET HERE

Playground link to code

【讨论】:

    猜你喜欢
    • 2020-06-20
    • 2020-02-01
    • 2015-10-23
    • 2019-07-29
    • 1970-01-01
    • 2021-12-10
    • 1970-01-01
    • 1970-01-01
    • 2019-08-21
    相关资源
    最近更新 更多