繁体   English   中英

TypeScript 中有没有办法从扩展固定接口的泛型中获取类型?

[英]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 ,类型TtransformResOne()方法的返回类型可以写为ReturnType<T['transformResOne']> ,使用ReturnType实用程序类型获取返回类型,而T[K] 索引访问运算符以获取键"transformResOne"处的T成员的类型。 并且可以对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的实现中,编译器不能真正遵循这种涉及条件类型(如ReturnType )的高阶类型杂耍,该条件类型依赖于未解析的泛型类型参数(如T )。

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

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 代码链接

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM