断言接口中的可选属性在编译时 class 中不为空

Assert that optional properties in interface are not null in class at compile time

在 Javascript 中,我喜欢将 options object 传递给 constructor,并使用默认值填写我期望的任何缺失属性:

function foo(options) {
   if(options.a == undefined) {
      options.a = 1;
   }
   if(options.b == undefined) {
      options.b = 'hello';
   }
   this.options = options;
}

new foo({a: 10});

尝试在 Typescript 中实现此模式,我有:

interface Options {
    a?: number;
    b?: string;
}

class Foo {
   options: Options;
   constructor(options: Options) {
      if(options.a == undefined) {
         options.a = 1;
      }
      if(options.b == undefined) {
         options.b = 'hello';
      }
      this.options = options;
   }
}

当我在class之后使用options时,我知道所有的属性都定义好了。但是,Typescript 并不知道这一点,因此该语句会产生错误:

let a:Number = this.options.a; // Argument of type 'number | undefined' is not assignable to parameter of type number.

此外,我不想让 class 中的代码稍后将 nullundefined 值分配给 属性:

this.options.a = undefined; // should produce compiler error

有没有办法定义我的类型,所以 Options 的属性在 class 中使用时不能为 null,而无需定义两个 Options 接口?

例如,我知道我可以制作另一个 interface 供内部使用:

interface OptionsSafe {
    a: number;
    b: string;
}

// in Foo constructor
this.options = {
    a: a != undefined ? options.a! : 1,
    b: b != undefined ? options.b! : 'hello'
};

但是我需要在两个单独的接口中定义选项的属性,并确保它们始终同步(如果 Typescript 可以从一个定义生成两个接口,这个解决方案会很棒)。

我也不想将所有属性作为单独的可选参数传递,因为

  1. 可以有几十个或更多选项
  2. 函数参数可能会偏离 Options 界面,所以我仍然需要保持两个 "interfaces" 同步。

一种选择是使用 Typescript 提供的内置 Partial 类型。这对您来说可能太接近 "defining two Options interfaces",但无论如何我都会告诉您我的意思。

Typescript 提供了一个内置类型 Partial,定义为 type Partial<T> = {[P in keyof T]?: T[P]}

这意味着您可以只定义具有所有成员的 Options 接口,但将其部分版本用于您的构造函数。像这样:

interface Options {
    a: number;
    b: string;
}

class Foo {
   options: Options;
   constructor(options: Partial<Options>) {
      const defaultOptions = {
          a: 1,
          b: 'hello'
      };

      this.options = Object.assign(defaultOptions, options);
   }
}

为了让 Typescript 正确推断您已获得对象的所有必要属性,您需要从您使用的 if 语句逻辑转移到更像上面的内容。