【问题标题】:Is there a way in TypeScript to get typing from a generic that extend a fixed interface?TypeScript 中有没有办法从扩展固定接口的泛型中获取类型?
【发布时间】:2021-05-10 13:28:37
【问题描述】:

问题是:

我有一个类,它有一些方法可以进行特定的 api 调用。此类获取作为构造函数参数的接口,其中有一组方法用于将 API 响应转换为其他内容。但是从API类的角度来看,没有必要知道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')

我已经尝试过使用泛型,我的最佳猜测是这样的:

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)
  }
}

当然不行!

【问题讨论】:

  • 这根本不使用泛型——你没有声明任何泛型类型参数。
  • 请考虑修改此问题中的代码以构成minimal reproducible example,将其放入像The TypeScript Playground (link to code) 这样的独立IDE 中时,可以清楚地说明您面临的问题。 (例如,唯一存在的错误应该是您遇到的错误;不应该有未声明的类型或值。)这将允许那些想要帮助您立即着手解决问题的人,而无需首先重新 -创造它。它将使您得到的任何答案都可以针对定义明确的用例进行测试。
  • @jcalz 这就是重点,我不知道我想做的事情到底怎么写,上面的表示是我能做的最好的。
  • @kaya3 使用泛型更新了代码,这里的重点是我不明白正确使用它们来获得所需结果的热。我使用我尝试过的方式更新了代码,但是,我知道,编译会到处抱怨
  • 因此,您可以执行this 之类的操作,但我更倾向于使用this,因为编译器以这种方式推断出更准确的类型。如果其中任何一个满足您的需求,我很乐意写一个答案;否则,请详细说明什么不适合您。

标签: typescript typescript-typings typescript-generics


【解决方案1】:

给定一个扩展IATransformer的泛型类型TT类型的transformResOne()方法的返回类型可以写成ReturnType&lt;T['transformResOne']&gt;,使用the ReturnType utility type得到返回类型,而T[K]indexed access operator 获取T 的成员的类型,键为"transformResOne"transformResTwo 也可以做类似的声明。这意味着我们可以像这样定义IAA

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 的实现中,编译器不能真正遵循这种涉及 conditional type (如 ReturnType)的高阶类型杂耍,这取决于未解析的 generic 类型参数,如 @987654343 @。

相反,它需要一些捷径,例如推断this.transformer.transformResOne(res) 返回的结果与IATransformertransformResOne(res) 方法所做的相同,即the any typeany 类型是故意松散的,因此如果您使用不当,编译器不会注意到或警告您:

getOops(param2: string): ReturnType<T['transformResTwo']> {
  const res = fetch2();
  return this.transformer.transformResOne(res); // no error, inferred as any
}

所以你必须小心这样的实现。


如果你想确保更多的类型安全,我建议尽可能避免使用这种条件类型。一种仍然允许良好类型推断的方法是重构以使返回类型 T1T2 中的所有内容都通用:

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'

看起来不错!

Playground link to code

【讨论】:

  • 精彩的解释!谢谢!
猜你喜欢
  • 1970-01-01
  • 2015-04-01
  • 2018-04-09
  • 1970-01-01
  • 2020-05-04
  • 2016-07-18
  • 2019-04-06
  • 1970-01-01
  • 2021-03-25
相关资源
最近更新 更多