首页 > 解决方案 > 如何避免 TypeScript 区分联合中的双重不可为空检查?

问题描述

在下面的代码中,TypeScript 不相信 if customMessageis undefinedclassNameis NOT undefined

export default class ClassRequiredInitializationHasNotBeenExecutedError extends Error {

  public static readonly NAME: string = "ClassRequiredInitializationHasNotBeenExecutedError";

  public constructor(
      parametersObject: {
        customMessage: string;
        className?: undefined;
      } | {
        className: string;
        customMessage?: undefined;
      }
  ) {

    super();

    this.name = ClassRequiredInitializationHasNotBeenExecutedError.NAME;

    if (typeof parametersObject.customMessage !== "undefined") {
      this.message = parametersObject.customMessage;
    } else {
      this.message = ClassRequiredInitializationHasNotBeenExecutedError.buildMessage({
          className: parametersObject.className
      })
    }
  }

  private static buildMessage(parametersObject: { className: string }): string {
    return `The class '${parametersObject.className}' has not been executed;`
  }
}

小提琴

错误:

Type 'string | undefined' is not assignable to type 'string'.
  Type 'undefined' is not assignable to type 'string'.(2322)

我知道如果会进行仔细检查,例如:

if (typeof parametersObject.customMessage !== "undefined") {
    this.message = parametersObject.customMessage;
} else if (typeof parametersObject.className !== "undefined") {
    this.message = ClassRequiredInitializationHasNotBeenExecutedError.buildMessage({
        className: parametersObject.className
})

上面的例子可以工作,但是:

  1. else if (typeof parametersObject.className !== "undefined")如果可能,我想避免
  2. 事件离开它,TypeScript 不会相信其中之一customMessageclassName已被初始化。这是关键时的示例:
let message: string;

if (typeof parametersObject.customMessage !== "undefined") {
    message = parametersObject.customMessage;
} else if (typeof parametersObject.className !== "undefined") {
    message = ClassRequiredInitializationHasNotBeenExecutedError.buildMessage({
        className: parametersObject.className
    })
}

console.log(message.length)
Variable 'message' is used before being assigned.(2454)

请注意,这个问题是关于如何让 TypeScript 相信 if customMessageis undefined, classNameis NOT undefined ,反之亦然,而不是如何初始化this.messageClassRequiredInitializationHasNotBeenExecutedError该类只是示例)

标签: typescript

解决方案


 parametersObject: {
        customMessage: string;
        className?: undefined;
      } | {
        className: string;
        customMessage?: undefined;
      }

可以简化为:

parametersObject: {
        customMessage: string;
      } | {
        className: string;
      }

因为property?: undefined几乎没有额外的信息。

那么条件可以表示为:

if ('customMessage' in parametersObject) {
  this.message = parametersObject.customMessage;
} else {
  this.message = ClassRequiredInitializationHasNotBeenExecutedError.buildMessage({
      className: parametersObject.className
  })
}

操场

更新

严格强制一个或另一个选项:

type Params<T extends {
        customMessage: string;
      } | {
        className: string;
      }>  = T extends {
        customMessage: any;
        className: any;
      }  ? never : T

export default class ClassRequiredInitializationHasNotBeenExecutedError<P extends {
        customMessage: string;
      } | {
        className: string;
      }> extends Error {

  public static readonly NAME: string = "ClassRequiredInitializationHasNotBeenExecutedError";

  public constructor(
      parametersObject: P extends Params<P> ? P : never
  ) {

    super();

    this.name = ClassRequiredInitializationHasNotBeenExecutedError.NAME;

    if ('customMessage' in parametersObject) {
      this.message = parametersObject.customMessage;
    } else {
      this.message = ClassRequiredInitializationHasNotBeenExecutedError.buildMessage({
          className: parametersObject.className
      })
    }
  }

  private static buildMessage(parametersObject: { className: string }): string {
    return `The class '${parametersObject.className}' has not been executed;`
  }
}

new ClassRequiredInitializationHasNotBeenExecutedError({ customMessage: ''  }) // ok
new ClassRequiredInitializationHasNotBeenExecutedError({ className: ''  }) // ok
new ClassRequiredInitializationHasNotBeenExecutedError({ customMessage: '', className: ''  }) // error as expected

操场


推荐阅读