微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

TypeScript - 作为类型保护的属性装饰器

如何解决TypeScript - 作为类型保护的属性装饰器

我想知道是否可以在 TypeScript 中使用 属性装饰器 作为 类型保护 以确保属性是不空|未定义

假设有以下示例。

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

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

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

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

解决方法

不,这在 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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。