TypeScript:访问通用对象以生成类型化的函数

时间:2019-08-05 08:46:42

标签: typescript typescript-typings

我有一个提供decode功能的界面

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

不,我有一个名为Service的类,该类提供的对象包含任意数量的这些Converter:

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和通用TConverter<T>)上运行的功能,传递给转换器。因此,不要这样做:

class Service<C extends Converters> {

  public write(name: string, value: any): void {

  }

}

我希望nameConverters中的任何键,而value是相应{{1}的ReturnType函数的decode }即Converter(所以基本上就是Converters[name]的{​​{1}})。

这是我最终想出的:

<T>

…但是它在不起作用

Converter<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"]>;
                                                                                   ^^^^^^^^^^^^^^

2 个答案:

答案 0 :(得分:2)

在开始之前有几件事:

  • 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.

Playground link


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

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

答案 1 :(得分:2)

您遇到的第一个问题是Converters的外观。那只是带有name类型的Converter<any>键的类型。您可能想写type Converters = { [name: string]: Converter<any> };的意思是带有索引签名的类型,这种类型会更接近,但在write调用中无法按预期工作,因为keyof C实际上会变成{{1 }},因为该界面可以有任何键。

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

K

Playground

修改 @Madara Uchiha♦ConverterValue版本比我的好,我会用它。

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", "");