Typescript类型,用于验证继承相同抽象类的抽象类和类实例。

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

Typescript type which validates abstract class and instance of class extending same abstract class

问题

以下是翻译好的内容:

我想在TypeScript中编写一个带有自定义要求的函数
- 函数可以命名为任何名称,让我们假设它为`speak`
- 函数必须接受两个参数,分别为`a``b`
- 参数`a`必须是一个抽象类,例如`speak(AbstractService)`(请参见示例以获得说明)
- 参数`b`必须是一个扩展参数`a`抽象类的类实例。例如`RuntimeUserService`。(请参见示例以获得说明)

以下是一些预期行为的示例,以代码形式编写。我们不会在`speak`函数内部使用任何代码,只需使用TypeScript的智能感知和错误提示

示例:

// 基本环境设置
abstract class SampleUserService { }
class RuntimeUserService implements SampleUserService { }
class DoNotWork { }

// 示例1:应该工作,因为RuntimeUserService实现/扩展了作为第一个参数提供的SampleUserService。
speak(SampleUserService, new RuntimeUserService);

// 示例2:不应该工作,因为RuntimeUserService的实例未被创建
speak(SampleUserService, RuntimeUserService);

// 示例3:不应该工作,因为DoNotWork未实现/扩展作为第一个参数提供的SampleUserService。
speak(SampleUserService, new DoNotWork());

// 示例4:不应该工作,因为它只是一个类声明,类似于示例2
speak(SampleUserService, DoNotWork); // 不应该工作

这是我尝试过的,但没有成功的地方。

type AbstractServiceConstructor<T> = abstract new (...args: any[]) => T;

function speak<T extends AbstractServiceConstructor<T>, U extends InstanceType<any>>(a: T, b: U) {
  // 函数体
}

abstract class SampleUserService { }
class RuntimeUserService extends SampleUserService { }
class DoNotWork { }

// 对于第一个参数会得到错误
speak(SampleUserService, new RuntimeUserService()); // 正常工作
speak(SampleUserService, RuntimeUserService); // 错误:类型“typeof RuntimeUserService”不能分配给类型“SampleUserService”
speak(SampleUserService, new DoNotWork()); // 错误:类型“DoNotWork”不能分配给类型“SampleUserService”
speak(SampleUserService, DoNotWork); // 错误:类型“typeof DoNotWork”不能分配给类型“SampleUserService”

希望这有助于您理解代码和要求。

英文:

I want to write a function in typescript with custom requirements:

  • Function can be named anything let's assume it to be speak
  • Function must accept two arguments as a and b respectively.
  • Argument a must be an abstract class for example speak(AbstractService) (See example for clarification)
  • Argument b must be a class instance which extends argument a abstract class . For example RuntimeUserService. (See example for clarification)

Here are some examples of expected behavior written in code. We will not be using any code inside speak function, rather just typescripts intellisense and errors are wanted.

Example:


// Basic Environment Setup
abstract class SampleUserService{}
class RuntimeUserService implements SampleUserService{}
class DoNotWork{}


// Example 1: Should work, because RuntimeUserService implements/extends SampleUserService provided as 1st argument.
speak(SampleUserService, new RuntimeUserService);

// Example 2: Should not work, as instance of RuntimeUserService is not created
speak(SampleUserService, RuntimeUserService);

// Example 3: Should not work as DoNotWork does not implement/extends SampleUserService provided as 1st argument.
speak(SampleUserService, new DoNotWork());

// Example 4: Do not work as it is just a class declaration similar to example 2
speak(SampleUserService, DoNotWork); // should not work

Here is what I have tried, but with no success.

type AbstractServiceConstructor&lt;T&gt; = abstract new (...args: any[]) =&gt; T;

function speak&lt;T extends AbstractServiceConstructor&lt;T&gt;, U extends InstanceType&lt;any&gt;&gt;(a: T, b: U) {
  // Function body
}

abstract class SampleUserService {}
class RuntimeUserService extends SampleUserService {}
class DoNotWork {}


// Gets errors for 1st argument
speak(SampleUserService, new RuntimeUserService()); // Works
speak(SampleUserService, RuntimeUserService); // Error: Argument of type &#39;typeof RuntimeUserService&#39; is not assignable to parameter of type &#39;SampleUserService&#39;
speak(SampleUserService, new DoNotWork()); // Error: Argument of type &#39;DoNotWork&#39; is not assignable to parameter of type &#39;SampleUserService&#39;
speak(SampleUserService, DoNotWork); // Error: Argument of type &#39;typeof DoNotWork&#39; is not assignable to parameter of type &#39;SampleUserService&#39;

答案1

得分: 1

你的示例存在一个大问题,即你的类都是空的,这已知会让人感到困惑。 TypeScript 的类型系统在很大程度上是结构化的,而不是名义化的。 具有相同形状(相同成员属性)的两个对象类型被视为相同类型,即使它们有单独的声明。在 class Foo {}abstract class Bar {}interface Baz {}type Qux = {} 中声明的类型都与 {} 相同,因此彼此相同。 如果你想让 TS 接受 RuntimeUserService 但拒绝 DoNotWork,那么你需要确保它们具有不同的结构。 修复的简单方法是添加一些结构:

abstract class SampleUserService { a = 1; }
class RuntimeUserService extends SampleUserService { b = 2; }
class DoNotWork { c = 3; }

现在这三种类型是不同的;SampleUserService 有一个数值 a 属性;RuntimeUserService 有这个属性,再加上一个数值 b 属性;而 DoNotWork 有一个数值 c 属性,但没有已知的 a 属性。

一旦你这样做,你可以将 speak() 修复成这样:

function speak<T>(a: AbstractServiceConstructor<T>, b: T) { }

你只需要一个泛型类型参数 T 对应于实例类型。然后,a 的类型是 AbstractServiceConstructor<T>(即使是非抽象构造函数,它也会与结构匹配;参见abstract 构造函数签名的发布说明,但除非你有某些不寻常的理由拒绝它),而 b 的类型是 T。 让我们测试一下:

speak(SampleUserService, new RuntimeUserService); // okay
speak(SampleUserService, RuntimeUserService); // error
speak(SampleUserService, new DoNotWork()); // error
speak(SampleUserService, DoNotWork); // error

它只接受第二个参数作为传递给第一个参数的(在结构上相同的)实例的情况。

英文:

One big problem with your example is that your classes are all empty, which is known to confuse people. TypeScript's type system is largely structural and not nominal. Two object types with the same shape (same member properties) are considered to be the same type, even if they have separate declarations. The types declared in class Foo {} and abstract class Bar {} and interface Baz {} and type Qux = {} are all identical to {} and thus to each other. If you want TS to accept a RuntimeUserService but reject a DoNotWork, then you'll need to make sure that they have different structures. The easy way to fix that is to add some structure:

abstract class SampleUserService { a = 1; }
class RuntimeUserService extends SampleUserService { b = 2; }
class DoNotWork { c = 3; }

Now those three types are different; a SampleUserService has a numeric a property; a RuntimeUserService has that plus a numeric b property, and a DoNotWork has a numeric c property but no known a property.


Once you do that you can fix speak() to look like this:

function speak&lt;T&gt;(a: AbstractServiceConstructor&lt;T&gt;, b: T) { }

You only need one generic type parameter T corresponding to the instance type. Then a is of type AbstractServiceConstructor&lt;T&gt; (which will structurally match even non-abstract constructors; see the release notes for abstract construct signatures, but that should be okay unless you have some unusual reason for rejecting that), and b is of type T. Let's test it out:

speak(SampleUserService, new RuntimeUserService); // okay
speak(SampleUserService, RuntimeUserService); // error
speak(SampleUserService, new DoNotWork()); // error
speak(SampleUserService, DoNotWork); // error

It only accepts cases where the value passed as the second argument is (structurally identical to) an instance of the (possibly abstract) class constructor passed as the first argument.

Playground link to code

huangapple
  • 本文由 发表于 2023年7月3日 21:17:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/76605140.html
匿名

发表评论

匿名网友

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

确定