如何将区分联合技术应用于函数签名?
How to apply discriminating union technique to function signatures?
我想将区分联合技术应用于函数签名。
代码示例
type fun1 = (opts: {type: 'a', value: string}) => string;
type fun2 = (opts: {type: 'b', value: number}) => number;
type anyFun = fun1 | fun2;
class Clazz {
innerMethod<T extends anyFun>(opts: Parameters<T>[0]): ReturnType<T> {
return 123 as ReturnType<T>;
}
otherMethod() {
// Case 1 - FAILED
// expected result type: string
// actual result type: string | number
const result1 = this.innerMethod({type: 'a', value: 'str'});
// Case 2 - OK
// expected result type = actual type = string
const result2 = this.innerMethod<fun1>({type: 'a', value: 'str'});
// Case 3 - OK
// TS error that fun2 is incompatible with type: 'a'
const result3 = this.innerMethod<fun2>({type: 'a', value: 'str'});
}
}
我想以案例 1 的方式编写代码而无需显式类型声明,但 TypeScript 类型推断 returns 在这种情况下类型错误。
如何使用函数签名以这种方式编写代码?或者唯一的方法是更改函数签名类型?
更新。更多信息我想解决什么问题。
我有一个包装服务集的外观。
每个服务都有 API 并且其中一部分 API 是同步的 getter。
服务可以通过getter向其他服务请求信息。
Getter 可以描述为函数签名。
我想在两个地方使用这个签名:
- 实现 getter 的服务并且运行良好。
- 调用 getter 的服务,这里我遇到了区分联合的问题。
代码:
type Fork = number;
type Spoon = string;
type GetFork = (arg: {type: 'fork', id: number}) => Fork;
type GetSpoon = (arg: {type: 'spoon', id: number}) => Spoon;
interface Facade {
constructor(services: AbstractService[]);
// routes request to service which can execute getter
get(request): any;
}
class AbstractService<NeedGetters extends (arg: any) => any = never> {
facade!: Facade;
init(facade: Facade) {
this.facade = facade;
}
getFromFacade<T extends NeedGetters>(opts: Parameters<T>[0]): ReturnType<T> {
return this.facade.get(opts);
}
}
interface ForksService extends AbstractService {
getFork({type}: Parameters<GetFork>[0]): ReturnType<GetFork>;
}
interface SpoonsService extends AbstractService {
getSpoon({type}: Parameters<GetSpoon>[0]): ReturnType<GetSpoon>;
}
class StuffService extends AbstractService<GetFork | GetSpoon> {
someMethod() {
// TS infered type string | number
const fork = this.getFromFacade({type: 'fork', id: 25});
// TS infered type string | number
const spoon = this.getFromFacade({type: 'spoon', id: 36});
}
}
我不确定为什么在似乎没有这种类型的函数时将其表示为函数签名,但是如果我想编写一个允许类型推断的 innerMethod()
版本,我会这样做:
innerMethod<P extends Parameters<AnyFun>[0]>(
opts: P
): ReturnType<Extract<AnyFun, (opts: P) => any>> {
return 123 as any;
}
您的版本的问题是编译器无法从 Parameters<T>[0]
类型的值可靠地推断出 T extends AnyFun
。编译器不知道如何从中“逆向工程”正确的 T
类型。最可靠的类型推断形式是为编译器提供您要推断的类型的值,然后计算其他类型。
上面的类型P
就是传入的opts
参数的类型,我们是constraining它到Parameters<AnyFun>[0]
,也就是
{
type: "a";
value: string;
} | {
type: "b";
value: number;
}
我们有一个 P
,我们可以在 that 上使用 the Extract
utility type to get the member of AnyFun
whose first parameter is of type P
, and then use ReturnType
。你可以看到它现在可以工作了:
otherMethod() {
const result1 = this.innerMethod({ type: 'a', value: 'str' }); // string
result1.toUpperCase(); // okay
}
但正如我所说,用函数来表示这样的类型映射对我来说似乎很奇怪。在没有关于您的用例的情有可原的信息的情况下,我倾向于像这样重写您的代码:
type IOMapping = { a: string, b: number }
innerMethod<K extends keyof IOMapping>(
opts: { type: K, value: IOMapping[K] }
): IOMapping[K] {
return 123 as any;
}
它在类型推断方面的行为相似,但需要的类型操作要少得多。
我想将区分联合技术应用于函数签名。
代码示例
type fun1 = (opts: {type: 'a', value: string}) => string;
type fun2 = (opts: {type: 'b', value: number}) => number;
type anyFun = fun1 | fun2;
class Clazz {
innerMethod<T extends anyFun>(opts: Parameters<T>[0]): ReturnType<T> {
return 123 as ReturnType<T>;
}
otherMethod() {
// Case 1 - FAILED
// expected result type: string
// actual result type: string | number
const result1 = this.innerMethod({type: 'a', value: 'str'});
// Case 2 - OK
// expected result type = actual type = string
const result2 = this.innerMethod<fun1>({type: 'a', value: 'str'});
// Case 3 - OK
// TS error that fun2 is incompatible with type: 'a'
const result3 = this.innerMethod<fun2>({type: 'a', value: 'str'});
}
}
我想以案例 1 的方式编写代码而无需显式类型声明,但 TypeScript 类型推断 returns 在这种情况下类型错误。
如何使用函数签名以这种方式编写代码?或者唯一的方法是更改函数签名类型?
更新。更多信息我想解决什么问题。
我有一个包装服务集的外观。
每个服务都有 API 并且其中一部分 API 是同步的 getter。
服务可以通过getter向其他服务请求信息。
Getter 可以描述为函数签名。
我想在两个地方使用这个签名:
- 实现 getter 的服务并且运行良好。
- 调用 getter 的服务,这里我遇到了区分联合的问题。
代码:
type Fork = number;
type Spoon = string;
type GetFork = (arg: {type: 'fork', id: number}) => Fork;
type GetSpoon = (arg: {type: 'spoon', id: number}) => Spoon;
interface Facade {
constructor(services: AbstractService[]);
// routes request to service which can execute getter
get(request): any;
}
class AbstractService<NeedGetters extends (arg: any) => any = never> {
facade!: Facade;
init(facade: Facade) {
this.facade = facade;
}
getFromFacade<T extends NeedGetters>(opts: Parameters<T>[0]): ReturnType<T> {
return this.facade.get(opts);
}
}
interface ForksService extends AbstractService {
getFork({type}: Parameters<GetFork>[0]): ReturnType<GetFork>;
}
interface SpoonsService extends AbstractService {
getSpoon({type}: Parameters<GetSpoon>[0]): ReturnType<GetSpoon>;
}
class StuffService extends AbstractService<GetFork | GetSpoon> {
someMethod() {
// TS infered type string | number
const fork = this.getFromFacade({type: 'fork', id: 25});
// TS infered type string | number
const spoon = this.getFromFacade({type: 'spoon', id: 36});
}
}
我不确定为什么在似乎没有这种类型的函数时将其表示为函数签名,但是如果我想编写一个允许类型推断的 innerMethod()
版本,我会这样做:
innerMethod<P extends Parameters<AnyFun>[0]>(
opts: P
): ReturnType<Extract<AnyFun, (opts: P) => any>> {
return 123 as any;
}
您的版本的问题是编译器无法从 Parameters<T>[0]
类型的值可靠地推断出 T extends AnyFun
。编译器不知道如何从中“逆向工程”正确的 T
类型。最可靠的类型推断形式是为编译器提供您要推断的类型的值,然后计算其他类型。
上面的类型P
就是传入的opts
参数的类型,我们是constraining它到Parameters<AnyFun>[0]
,也就是
{
type: "a";
value: string;
} | {
type: "b";
value: number;
}
我们有一个 P
,我们可以在 that 上使用 the Extract
utility type to get the member of AnyFun
whose first parameter is of type P
, and then use ReturnType
。你可以看到它现在可以工作了:
otherMethod() {
const result1 = this.innerMethod({ type: 'a', value: 'str' }); // string
result1.toUpperCase(); // okay
}
但正如我所说,用函数来表示这样的类型映射对我来说似乎很奇怪。在没有关于您的用例的情有可原的信息的情况下,我倾向于像这样重写您的代码:
type IOMapping = { a: string, b: number }
innerMethod<K extends keyof IOMapping>(
opts: { type: K, value: IOMapping[K] }
): IOMapping[K] {
return 123 as any;
}
它在类型推断方面的行为相似,但需要的类型操作要少得多。