[英]Is there a way in TypeScript to get typing from a generic that extend a fixed interface?
问题是:
我有一个 class 有一些方法可以进行特定的 api 调用。 这个 class 作为构造函数参数获取一个接口,其中有一组方法用于将 API 响应转换为其他内容。 但是从API class来看,是不需要知道transformer实现的,这种方式可以用任何方式实现transformer。 我想做的是在一切都被实例化后知道返回的类型。
所需行为的代码示例
type SomeObj = {
stringKey: string;
numberKey: number;
}
type SomeOtherObj = {
stringKey: string;
numberKey: number;
}
function fetch1() {
return { stringKey: 'stringKey', numberKey: 2 }
}
function fetch2() {
return { stringKey: 'stringKey', numberKey: 2 }
}
interface IATransformer {
transformResOne: (res1: SomeObj) => any;
transformResTwo: (res2: SomeOtherObj) => any;
}
class ATransformer {
transformResOne(res1: SomeObj): string {
return res1.stringKey
}
transformResTwo(res2: SomeOtherObj): number {
return res2.numberKey;
}
}
interface IA<T extends ATransformer> {
getOne: (param: string) => any; // Not "any" but something from the generic
getTwo: (param2: string) => any; // Not "any" but something from the generic
}
class A<T extends ATransformer> implements IA<T> {
constructor(
private readonly transformer: T
) { }
// Return type doesn't work in this way but don't know how to sobstitute it
getOne(param: string): ReturnType<this.transformer.getOne> {
const res = fetch1()
return this.transformer.transformResOne(res)
}
// Return type doesn't work in this way but don't know how to sobstitute it
getTwo(param2: string): ReturnType<this.transformer.getTwo> {
const res = fetch2();
return this.transformer.transformResTwo(res)
}
}
const transformer = new ATransformer();
// Generic is inferred from the parameter
const a = new A(transformer);
// Desired result ---> ERROR: the type returned from the function is not number but string
const resOneResult: number = a.getOne('Some string')
我已经尝试过使用 generics 并且我最好的猜测是这样的:
class A implements IA {
constructor(
private readonly transformer: IATransformer
) { }
getOne(param: string): ReturnType<typeof this.transformer.transformResOne> {
const res = fetch1()
return this.transformer.transformResOne(res)
}
getTwo(param2: string): ReturnType<typeof this.transformer.transformResTwo> {
const res = fetch2();
return this.transformer.transformResTwo(res)
}
}
但是当然不行!
给定一个扩展IATransformer
的泛型类型T
,类型T
的transformResOne()
方法的返回类型可以写为ReturnType<T['transformResOne']>
,使用ReturnType
实用程序类型获取返回类型,而T[K]
索引访问运算符以获取键"transformResOne"
处的T
成员的类型。 并且可以对transformResTwo
做出类似的声明。 这意味着我们可以这样定义IA
和A
:
interface IA<T extends IATransformer> {
getOne: (param: string) => ReturnType<T['transformResOne']>;
getTwo: (param2: string) => ReturnType<T['transformResTwo']>;
}
class A<T extends IATransformer> implements IA<T> {
constructor(
private readonly transformer: T
) { }
getOne(param: string): ReturnType<T['transformResOne']> {
const res = fetch1()
return this.transformer.transformResOne(res)
}
getTwo(param2: string): ReturnType<T['transformResTwo']> {
const res = fetch2();
return this.transformer.transformResTwo(res)
}
}
这应该可以按您的意愿工作:
const transformer = new ATransformer();
const a = new A(transformer);
const resOneResult: number = a.getOne('Some string'); // error!
// Type 'string' is not assignable to type 'number'
但是,请注意,在A
的实现中,编译器不能真正遵循这种涉及条件类型(如ReturnType
)的高阶类型杂耍,该条件类型依赖于未解析的泛型类型参数(如T
)。
相反,它需要一些捷径,例如推断this.transformer.transformResOne(res)
返回与IATransformer
的transformResOne(res)
方法所做的相同的事情,即any
type 。 any
类型是故意松散的,因此如果您错误地使用它,编译器不会注意到或警告您:
getOops(param2: string): ReturnType<T['transformResTwo']> {
const res = fetch2();
return this.transformer.transformResOne(res); // no error, inferred as any
}
所以你必须小心这样的实现。
如果你想确保更多的类型安全,我建议尽可能避免使用这种条件类型。 一种仍然允许良好类型推断的方法是重构以使所有返回类型T1
和T2
中的所有内容都通用:
interface IA<T1, T2> {
getOne: (param: string) => T1;
getTwo: (param2: string) => T2;
}
interface IATransformer<T1, T2> {
transformResOne: (res1: SomeObj) => T1;
transformResTwo: (res2: SomeOtherObj) => T2;
}
class A<T1, T2> implements IA<T1, T2> {
constructor(
private readonly transformer: IATransformer<T1, T2>
) { }
getOne(param: string) {
const res = fetch1()
return this.transformer.transformResOne(res)
}
getTwo(param2: string) {
const res = fetch2();
return this.transformer.transformResTwo(res)
}
getOops(param2: string): T2 {
const res = fetch2();
return this.transformer.transformResOne(res); // error!
}
}
正如您所提到的,您可能不想对具有大量类型参数的东西执行此操作,但是您可以看到编译器在捕获错误和推断类型方面要好得多(您甚至不必注释返回类型getOne()
和getTwo()
)。 让我们确保它仍然有效:
const transformer = new ATransformer();
const a = new A(transformer);
const resOneResult: number = a.getOne('Some string'); // error!
// Type 'string' is not assignable to type 'number'
看起来不错!
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.