TypeScript:访问泛型对象以生成类型化函数



我有一个提供decode函数的接口

interface Converter<T> {
  uuid: CUUID;
  decode: (value: T) => Buffer;
  encode?: (value: Buffer) => T;
}

不,我有一个名为 Service 的类,它提供了一个包含任意数量的转换器的对象:

type Converters = { name: Converter<any> };
class Service<C extends Converters> {
}

这样我就可以将这些对象传递给Service

interface MyConverters extends Converters {
  state: Converter<string>;
  water: Converter<number>;
}
const service = new Service<MyConverters>();

Service类有几个函数,应该在传递给转换器的Converters和泛型T(Converter<T>(上工作。因此,与其这样做:

class Service<C extends Converters> {
  public write(name: string, value: any): void {
  }
}

我希望nameConverters中的任何键,valueConverters[name]的相应Converterdecode函数的ReturnType(所以基本上是Converter<T><T>(。

这是我最终想到的:

interface Converter<T> {
  uuid: CUUID;
  decode: (value: T) => Buffer;
  encode?: (value: Buffer) => T;
}
type Converters = { name: Converter<any> };
type ConverterNames<C extends Converters> = keyof C;
type ConverterValue<C extends Converters, N extends Keys<C>> = ReturnType<C[N]["decode"]>;
class Service<C extends Converters> {
  public write<N extends ConverterNames<C>>(
    name: N,
    value: ConverterValue<C, N>
  ): void {}
}

。但它不起作用

type ConverterValue<C extends Converters, N extends ConverterNames<C>> = ReturnType<C[N]["decode"]>;
                                                                                   ^^^^^^^^^^^^^^

我收到错误

Type 'C[N]["decode"]' does not satisfy the constraint '(...args: any) => any'.
  Type 'C[keyof C]["decode"]' is not assignable to type '(...args: any) => any'.
    Type 'C[string]["decode"] | C[number]["decode"] | C[symbol]["decode"]' is not assignable to type '(...args: any) => any'.
      Type 'C[string]["decode"]' is not assignable to type '(...args: any) => any'.ts(2344)
Type '"decode"' cannot be used to index type 'C[N]'.ts(2536)

我不确定发生了什么。

最终,我想使这成为可能:

interface MyConverters extends Converters {
  state: Converter<string>;
  water: Converter<number>;
}
const service = new Service<MyConverters>();
service.write("water", 12);
                 ^      ^
                          checks if these 2 types match MyConverters

在我们开始之前,有几件事:

  • type Converters = { name: Converter<any> }; 不是一个键映射到Converter<any>的对象,它是一个具有一个键的对象,name ,映射到一个Converter<any>,我怀疑你想要类似type Converters = { [name: string]: Converter<any>; }的东西,但这也行不通。见下文。
  • 我认为不可能将泛型限制为 { [name: string]: Converter<any>; },因为任何扩展类型都需要提供索引签名(即允许访问任何字符串,这不是您想要的。

我来过的最好的是

interface Converter<T> {
    uuid: CUUID;
    decode: (value: T) => Buffer;
    encode?: (value: Buffer) => T;
}

class Service<C> {
    public write<K extends keyof C>(
        name: K,
        value: C[K] extends Converter<infer R> ? R : never
    ): void { }
}
interface MyConverters {
    state: Converter<string>;
    water: Converter<number>;
}
const service = new Service<MyConverters>();
service.write('water', 'foo'); // Error, expected number.

游乐场链接


简而言之,我放弃了extends Converters,转而支持条件推理。另外,我从Converter<T>而不是ReturnType<Converter<T>['decoder']>推断T,无论如何,看看ReturnType如何在幕后使用条件推理,这样似乎更简单。

如果尝试传递不是转换器的密钥,则无论提供什么值,write调用都不会编译。

你遇到的第一个问题是Converters的外观。那只是一个类型,具有 Converter<any> 类型的name键。您可能想编写type Converters = { [name: string]: Converter<any> };表示具有索引签名的类型,该类型会更接近,但在write调用上不会按预期工作,因为keyof C实际上最终会被string | number,因为接口可以有任何键。

解决方案是使用 Record<K, Converter<any> 作为约束,其中K将是我们要传入的任何类型的键:

interface Converter<T> {
  uuid: CUUID;
  decode: (value: T) => Buffer;
  encode?: (value: Buffer) => T;
}

type Converters<K extends PropertyKey> = Record<K, Converter<any>>

type ConverterValue<C extends Converters<keyof C>, N extends keyof C> = Parameters<C[N]["decode"]>[0];
class Service<C extends Converters<keyof C>> {
  public write<N extends keyof C>(
    name: N,
    value: ConverterValue<C, N>
  ): void {}
}
interface MyConverters extends Converters<keyof MyConverters> {
  state: Converter<string>;
  water: Converter<number>;
}
const service = new Service<MyConverters>();
service.write("water", 12);
service.write("water", "12"); // err
service.write("state", "");

操场

编辑@Madara宇智波♦版本的 ConverterValue 比我的更好,我会使用它。

type ConverterValue<C extends Converters<keyof C>, N extends keyof C> = C[N] extends Converter<infer R> ? R : never;

最新更新