如何从泛型类型获取实例类型?

huangapple go评论78阅读模式
英文:

How to get an instance type from generic type?

问题

我有以下的类:

export class ValidationFailedError extends Error {
  public errors: ValidationError[];

  constructor(errors: ValidationError[]) {
    super('Validation errors exist');

    this.errors = errors;
  }
}

export class ValidationError {}
export class DuplicateFileValidationError extends ValidationError {}
export class MultipleTransactionTypesValidationError extends ValidationError {}
export class FileIsPlacedInWrongFolderValidationError extends ValidationError {
  constructor(public fileName: string, public expectedFolder: string, public actualFolder: string) {
    super();
  }
}
export class InvalidTransactionsCountValidationError extends ValidationError {
  constructor(
    public transactionsCount: number,
    public minTransactionsCount: number,
    public maxTransactionsCount: number,
  ) {
    super();
  }
}

我有一些测试(我使用Jest),用来检查函数是否抛出了ValidationFailedError,然后还检查ValidationFailedError.errors数组中的内容。为了这些测试,我编写了以下的辅助函数,它检查ValidationFailedError错误是否只包含一个指定的ValidationError错误,并执行一些自定义的断言,这些断言以回调函数的形式提供,该回调函数以ValidationError作为参数,参数的类型是预期的错误类型:

function expectValidationFailedErrorContainsSingleError<T extends ValidationError>(
  validationFailedError: ValidationFailedError,
  errorCtor: T,
  customAssertAction?: (err: T) => void,
): void {
  const { errors } = validationFailedError;
  expect(Array.isArray(errors)).toBe(true);
  expect(errors.length).toEqual(1);

  const validationError = errors[0] as T;
  expect(validationError).toBeInstanceOf(errorCtor);

  customAssertAction(validationError);
}

上面的函数编译没有问题,但是在我的测试中使用这个函数时出现了问题。请看下面的示例代码,这段代码是我从我的一个测试中摘取的:

expectValidationFailedErrorContainsSingleError(
  err,
  FileIsPlacedInWrongFolderValidationError,
  (validationError) => {
    expect(validationError.fileName).toEqual('test-file.json');
    expect(validationError.actualFolder).toEqual(actualFolder);
    expect(validationError.expectedFolder).toEqual(expectedFolder);
  },
);

TypeScript无法识别validationError参数的类型,它会报出以下3个错误:

Property 'fileName' does not exist on type 'typeof
FileIsPlacedInWrongFolderValidationError'.ts(2339)

Property 'actualFolder' does not exist on type 'typeof
FileIsPlacedInWrongFolderValidationError'.ts(2339)

Property 'expectedFolder' does not exist on type 'typeof
FileIsPlacedInWrongFolderValidationError'.ts(2339)

当我将鼠标悬停在validationError参数上时,我发现该参数的类型是typeof FileIsPlacedInWrongFolderValidationError

如何从泛型类型获取实例类型?

我感觉validationError的类型不是实例类型,而是类类型。

问题: 有没有办法解决这个问题,这样我就可以在函数中访问fileNameactualFolderexpectedFolder属性了?

英文:

I have the following classes:

export class ValidationFailedError extends Error {
  public errors: ValidationError[];

  constructor(errors: ValidationError[]) {
    super(&#39;Validation errors exist&#39;);

    this.errors = errors;
  }
}

export class ValidationError {}
export class DuplicateFileValidationError extends ValidationError {}
export class MultipleTransactionTypesValidationError extends ValidationError {}
export class FileIsPlacedInWrongFolderValidationError extends ValidationError {
  constructor(public fileName: string, public expectedFolder: string, public actualFolder: string) {
    super();
  }
}
export class InvalidTransactionsCountValidationError extends ValidationError {
  constructor(
    public transactionsCount: number,
    public minTransactionsCount: number,
    public maxTransactionsCount: number,
  ) {
    super();
  }
}

And I have some tests (I'm using Jest) that check if function throws a ValidationFailedError, and then also checks, what is inside ValidationFailedError.errors array. For the tests I wrote the following helper function which checks that ValidationFailedError error contains only one error of provided ValidationError, and performs some custom asserts provided as a callback function which takes single argument - ValidationError with expected type as an argument:

function expectValidationFailedErrorContainsSingleError&lt;T extends ValidationError&gt;(
  validationFailedError: ValidationFailedError,
  errorCtor: T,
  customAssertAction?: (err: T) =&gt; void,
): void {
  const { errors } = validationFailedError;
  expect(Array.isArray(errors)).toBe(true);
  expect(errors.length).toEqual(1);

  const validationError = errors[0] as T;
  expect(validationError).toBeInstanceOf(errorCtor);

  customAssertAction(validationError);
}

The function above is compiled fine, however I have an issue with using this function in my tests. Please take a look at the example below, I took this piece of code from one of my tests:

expectValidationFailedErrorContainsSingleError(
  err,
  FileIsPlacedInWrongFolderValidationError,
  (validationError) =&gt; {
    expect(validationError.fileName).toEqual(&#39;test-file.json&#39;);
    expect(validationError.actualFolder).toEqual(actualFolder);
    expect(validationError.expectedFolder).toEqual(expectedFolder);
  },
);

Typescript does not recognize the type of validationError parameter, it raises the following 3 errors:

> Property 'fileName' does not exist on type 'typeof
> FileIsPlacedInWrongFolderValidationError'.ts(2339)
>
> Property 'actualFolder' does not exist on type 'typeof
> FileIsPlacedInWrongFolderValidationError'.ts(2339)
>
> Property 'expectedFolder' does not exist on type 'typeof
> FileIsPlacedInWrongFolderValidationError'.ts(2339)

When I hover on validationError parameter, I see that the type of this parameter is typeof FileIsPlacedInWrongFolderValidationError :

如何从泛型类型获取实例类型?

I feel like the type of validationError is not an instance type, it's a class type.

The Question: Is it possible to fix this issue, so I can access the properties fileName, actualFolder and expectedFolder in the function?

答案1

得分: 1

假设您不打算调用errorCtor(因为您无法确定它在这里是否有参数或有多少个参数),此类型在TS Playground上没有显示任何警告:

function expectValidationFailedErrorContainsSingleError<T extends ValidationError>(
  validationFailedError: ValidationFailedError,
  errorCtor: new (...args: never[]) => T, // 构造函数类型
  customAssertAction?: (err: T) => void,
): void {
  const { errors } = validationFailedError;
  expect(Array.isArray(errors)).toBe(true);
  expect(errors.length).toEqual(1);

  const validationError = errors[0] as T;
  expect(validationError).toBeInstanceOf(errorCtor);

  customAssertAction?.(validationError); // 注意可选链的使用
}
const err = new ValidationFailedError([
  new FileIsPlacedInWrongFolderValidationError('test-file.json', '/etc/ts-test', '/etc/ts_test')
])
expectValidationFailedErrorContainsSingleError(
  err,
  FileIsPlacedInWrongFolderValidationError,
  (validationError) => {
    const { fileName, actualFolder, expectedFolder } = validationError
    expect(fileName).toEqual('test-file.json');
    expect(actualFolder).toEqual('/etc/ts-test');
    expect(expectedFolder).toEqual('/etc/ts_test');
  },
);
英文:

Assuming you don't plan on calling the errorCtor (since you can't know if or how many arguments it has here), this typing doesn't show any warnings on TS Playground:

function expectValidationFailedErrorContainsSingleError&lt;T extends ValidationError&gt;(
  validationFailedError: ValidationFailedError,
  errorCtor: new (...args: never[]) =&gt; T, // Constructor type
  customAssertAction?: (err: T) =&gt; void,
): void {
  const { errors } = validationFailedError;
  expect(Array.isArray(errors)).toBe(true);
  expect(errors.length).toEqual(1);

  const validationError = errors[0] as T;
  expect(validationError).toBeInstanceOf(errorCtor);

  customAssertAction?.(validationError); // Note the optional chaining
}

const err = new ValidationFailedError([
  new FileIsPlacedInWrongFolderValidationError(&#39;test-file.json&#39;, &#39;/etc/ts-test&#39;, &#39;/etc/ts_test&#39;)
])
expectValidationFailedErrorContainsSingleError(
  err,
  FileIsPlacedInWrongFolderValidationError,
  (validationError) =&gt; {
    const { fileName, actualFolder, expectedFolder } = validationError
    expect(fileName).toEqual(&#39;test-file.json&#39;);
    expect(actualFolder).toEqual(&#39;/etc/ts-test&#39;);
    expect(expectedFolder).toEqual(&#39;/etc/ts_test&#39;);
  },
);

huangapple
  • 本文由 发表于 2023年8月8日 22:11:46
  • 转载请务必保留本文链接:https://go.coder-hub.com/76860393.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定