Joe*_*app 12 generics restriction typescript typescript-generics
在 Typescript 中,如何在编译时将泛型类型限制为多个类之一?例如,你如何实现这个伪代码?
class VariablyTyped<T has one of types A or B or C> {
method(hasOneType: T) {
if T has type A:
do something assuming type A
if T has type B:
do something assuming type B
if T has type C:
do something assuming type C
}
}
Run Code Online (Sandbox Code Playgroud)
此外,我希望能够将属性(或任何变量)分配给通用类型选项之一的特定后代类型,而不仅仅是给定类型之一。例如:
class VariablyTyped<T has one of types A or B or C> {
descendentClassOfT: T
method(hasOneType: T) {
descendentClassOfT = hasOneType
}
}
class D extends class C {
methodUniqueToD() { }
}
const v = new VariablyTyped(new D())
v.descendentClassOfT.methodUniqueToD()
Run Code Online (Sandbox Code Playgroud)
这个答案显然并不明显,因为我花了几个小时。在我看来,已经有人问过这个问题的某种形式,但给出的解决方案甚至不适合我。可能之前的问题仅在非常具体的情况下得到回答,因为赞成票的数量表明它正在解决某些人的问题。
我发布这个新问题是为了清楚地说明一般问题并跟进解决方案。
Joe*_*app 10
我为此纠结了几个小时,但回想起来,解决方案似乎很明显。我首先提出解决方案,然后将其与先前的方法进行比较。(在 Typescript 2.6.2 中测试。)
// WORKING SOLUTION: union of types with type checks
class MustBeThis {
method1() { }
}
class OrThis {
method2() { }
}
abstract class OrOfThisBaseType {
method3a() { }
}
class ExtendsBaseType extends OrOfThisBaseType {
method3b() { }
}
class GoodVariablyTyped<T extends MustBeThis | OrThis | OrOfThisBaseType> {
extendsBaseType: T;
constructor(hasOneType: T) {
if (hasOneType instanceof MustBeThis) {
hasOneType.method1();
}
else if (hasOneType instanceof OrThis) {
hasOneType.method2();
}
// either type-check here (as implemented) or typecast (commented out)
else if (hasOneType instanceof OrOfThisBaseType) {
hasOneType.method3a();
// (<OrOfThisBaseType>hasOneType).method3a();
this.extendsBaseType = hasOneType;
}
}
}
Run Code Online (Sandbox Code Playgroud)
此解决方案的以下检查编译得很好:
const g1 = new GoodVariablyTyped(new MustBeThis());
const g1t = new GoodVariablyTyped<MustBeThis>(new MustBeThis());
const g1e: MustBeThis = g1.extendsBaseType;
const g1te: MustBeThis = g1t.extendsBaseType;
const g2 = new GoodVariablyTyped(new OrThis());
const g2t = new GoodVariablyTyped<OrThis>(new OrThis());
const g2e: OrThis = g2.extendsBaseType;
const g2te: OrThis = g2t.extendsBaseType;
const g3 = new GoodVariablyTyped(new ExtendsBaseType());
const g3t = new GoodVariablyTyped<ExtendsBaseType>(new ExtendsBaseType());
const g3e: ExtendsBaseType = g3.extendsBaseType;
const g3te: ExtendsBaseType = g3t.extendsBaseType;
Run Code Online (Sandbox Code Playgroud)
Compare the above approach with the previously accepted answer that declared the generic to be the intersection of the class options:
// NON-WORKING SOLUTION A: intersection of types
class BadVariablyTyped_A<T extends MustBeThis & OrThis & OrOfThisBaseType> {
extendsBaseType: T;
constructor(hasOneType: T) {
if (hasOneType instanceof MustBeThis) {
(<MustBeThis>hasOneType).method1();
}
// ERROR: The left-hand side of an 'instanceof' expression must be of type
// 'any', an object type or a type parameter. (parameter) hasOneType: never
else if (hasOneType instanceof OrThis) {
(<OrThis>hasOneType).method2();
}
else {
(<OrOfThisBaseType>hasOneType).method3a();
this.extendsBaseType = hasOneType;
}
}
}
// ERROR: Property 'method2' is missing in type 'MustBeThis'.
const b1_A = new BadVariablyTyped_A(new MustBeThis());
// ERROR: Property 'method2' is missing in type 'MustBeThis'.
const b1t_A = new BadVariablyTyped_A<MustBeThis>(new MustBeThis());
// ERROR: Property 'method1' is missing in type 'OrThis'.
const b2_A = new BadVariablyTyped_A(new OrThis());
// ERROR: Property 'method1' is missing in type 'OrThis'.
const b2t_A = new BadVariablyTyped_A<OrThis>(new OrThis());
// ERROR: Property 'method1' is missing in type 'ExtendsBaseType'.
const b3_A = new BadVariablyTyped_A(new ExtendsBaseType());
// ERROR: Property 'method1' is missing in type 'ExtendsBaseType'.
const b3t_A = new BadVariablyTyped_A<ExtendsBaseType>(new ExtendsBaseType());
Run Code Online (Sandbox Code Playgroud)
Also compare the above working approach with another suggested solution in which the generic type is constrained to extend an interface that implements all of the class interface options. The errors occurring here suggest that it is logically identical to the prior non-working solution.
// NON-WORKING SOLUTION B: multiply-extended interface
interface VariableType extends MustBeThis, OrThis, OrOfThisBaseType { }
class BadVariablyTyped_B<T extends VariableType> {
extendsBaseType: T;
constructor(hasOneType: T) {
if (hasOneType instanceof MustBeThis) {
(<MustBeThis>hasOneType).method1();
}
// ERROR: The left-hand side of an 'instanceof' expression must be of type
// 'any', an object type or a type parameter. (parameter) hasOneType: never
else if (hasOneType instanceof OrThis) {
(<OrThis>hasOneType).method2();
}
else {
(<OrOfThisBaseType>hasOneType).method3a();
this.extendsBaseType = hasOneType;
}
}
}
// ERROR: Property 'method2' is missing in type 'MustBeThis'.
const b1_B = new BadVariablyTyped_B(new MustBeThis());
// ERROR: Property 'method2' is missing in type 'MustBeThis'.
const b1t_B = new BadVariablyTyped_B<MustBeThis>(new MustBeThis());
// ERROR: Property 'method1' is missing in type 'OrThis'.
const b2_B = new BadVariablyTyped_B(new OrThis());
// ERROR: Property 'method1' is missing in type 'OrThis'.
const b2t_B = new BadVariablyTyped_B<OrThis>(new OrThis());
// ERROR: Property 'method1' is missing in type 'ExtendsBaseType'.
const b3_B = new BadVariablyTyped_B(new ExtendsBaseType());
// ERROR: Property 'method1' is missing in type 'ExtendsBaseType'.
const bt_B = new BadVariablyTyped_B<ExtendsBaseType>(new ExtendsBaseType());
Run Code Online (Sandbox Code Playgroud)
Ironically, I later solved my app-specific problem without having to constrain the generic type. Perhaps others should learn from my lesson and first try to find another, better way to do the job.
| 归档时间: |
|
| 查看次数: |
5265 次 |
| 最近记录: |