我有一个提供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 {
}
}
我希望name
是Converters
中的任何键,value
是Converters[name]
的相应Converter
的decode
函数的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;