在构造函数中处理Typescript类型检查

beq87vna  于 2023-02-05  发布在  TypeScript
关注(0)|答案(2)|浏览(196)

下面是一个非常做作的例子:
如果对象的某个属性有特定的值,那么必须定义另一个(可选的)属性。我希望在构造函数中捕获错误,以便用户能够立即捕获问题。
在后面的方法中,我想依赖于我们之前执行的类型检查。有没有一种方法可以向下“级联”这种检查,或者利用我们在类的其他地方的构造函数中所做的检查?通常,有没有一种更优雅的方法来构造这种类型的流?

interface ExampleOptions {
  someType: "basic" | "needsExtra"
  extra?: string  
}

class Example {
  readonly someType: "basic" | "needsExtra";
  readonly extra?: string;
  
  constructor(opts: ExampleOptions){
    this.someType = opts.someType
    this.extra = opts.extra
    
    // handle type checking here to raise error immediately
    if (this.someType === "needsExtra" && !this.extra) {
      throw new Error("gotta define extra when using type 'needsExtra'")
    }
  }

  doSomething() {
   return this.someType === "basic" ? 
    Example.helper(this.someType) : Example.helperExtra(this.someType, this.extra) // this throws
  }

  private static helper(someType: string): string {
    return someType
  }

  private static helperExtra(someType: string, extra: string) {
    return `${someType}${extra}`
  }
}

doSomething()方法引发异常:

Argument of type 'string | undefined' is not assignable to parameter of type 'string'.
  Type 'undefined' is not assignable to type 'string'.(2345)

即使我们从构造函数中知道这个值将被初始化。我总是可以在方法中添加另一个检查,但那将是多余的。而且我相信使用!(非空Assert)操作符是不受欢迎的。

kdfy810k

kdfy810k1#

doSomething()方法告诉我你把两个类压缩成了一个类,构造函数也显示了同样的内容,但它也有把类作为属性容器使用的问题,而不是真实的的OOP。
对于更干净的代码、真实的的OOP和多态性,我的建议是声明一个接口,并在两个或更多的类中实现它,这些类实现了当前代码的替代路径:

interface Example {
  doSomething();
}

class BasicExample implements Example {
  // this class does not even need a constructor

  public doSomething() {
    return 'basic';
  }
}

class AdvancedExample implements Example {
  public constructor(private extra: string) { 
    // depending on what you understand by `!this.extra`, this constructor
    // might need or might not need to do anything more
    // the TypeScript compiler already prevents invoking the constructor
    // without a value for parameter `extra`
  }

  public doSomething() {
    return `needsExtra${extra}`;
  }
}

很明显,您在问题中发布的示例代码过于简化,真实的代码比我上面所做的要多。
然而,通过将不同的东西分离到不同的类中,可以使代码更干净、更有组织、更容易阅读和理解。

5gfr0r5j

5gfr0r5j2#

使用两个类型的联合,而不是单个类型。

更多

完整简化示例:

type ExampleOptions = {
    someType: "basic" | "needsExtra"
    extra?: string
}

type Better =
    | { someType: 'basic' }
    | { someType: 'needsExtra', extra: string }

class Example {
    constructor(readonly config: Better) { }
    doSomething() {
        return this.config.someType === "basic" ?
            [this.config.someType] : [this.config.someType, this.config.extra]; // OK! 
    }
}

相关问题