如何从具有类型约束的泛型函数正确推断 RxJS Subject<T> 的 T
How to correctly infer T for an RxJS Subject<T> from generic function with type constraint
我有一个模块可以让呼叫者订阅一组离散的事件。当调用者订阅给定事件时,他们提供事件名称作为第一个参数。从那个论点,我希望能够推断出回调签名。
该实现为每个受支持的事件使用 RxJS Subject
,并为每次调用 myModule.subscribe(eventType)
创建对其的订阅。下面显示了实施的精简版本(您还可以在 this TS Playground workspace 上看到这个 运行)
import { Subject, Subscription } from "rxjs";
const EventNames = {
a: "a",
b: "b",
c: "c",
} as const;
type Payloads = {
[EventNames.a]: string;
[EventNames.b]: number;
[EventNames.c]: boolean;
};
type EventTypes = keyof typeof EventNames;
// all possible event objects
type Events<T> = T extends EventTypes ? {type: T, payload: Payloads[T]} : never;
// all possible callbacks
type Callback<T> = T extends EventTypes ? (event: Events<T>) => void : never;
// all possible subjects
type Subjects<T> = T extends EventTypes ? Subject<Events<T>> : never;
const collection = new Map<EventTypes, Subjects<EventTypes>>();
function fnWithConstraint<T extends EventTypes>(
name: T,
cb: Callback<T>
): Subscription | null {
const subject = collection.has(name)
? collection.get(name)
: new Subject<Events<T>>();
if (subject !== undefined) {
collection.set(name, subject);
/* ^ Type '{ type: "a"; payload: string; }' is not assignable
to type 'Events<T>'.
*/
const subscription = subject.subscribe(cb)
/* ^ This expression is not callable. Each member of the union
type '{ (observer?: Partial<Observer<Events<T>>> | undefined):
Subscription; (next: (value: Events<T>) => void): Subscription;
... 1 more ...' has signatures, but none of those signatures
are compatible with each other.
*/
return subscription;
}
return null;
}
fnWithConstraint("b", (event) => console.log(event));
// expect typeof event -> { type: "b"; payload: number; }
我无法让它编译成功。我链接到的 TS Playground 显示了我在 line:45 上获得的正确结果,但编译器抱怨 Subjects 类型签名,我似乎无法解决它。我错过了什么?
你只需要重载你的函数:
import { Subject, Subscription } from "rxjs";
const EventNames = {
a: "a",
b: "b",
c: "c",
} as const;
type Payloads = {
[EventNames.a]: string;
[EventNames.b]: number;
[EventNames.c]: boolean;
};
type EventTypes = keyof typeof EventNames;
// all possible event objects
type Events<T extends EventTypes> = { type: T, payload: Payloads[T] }
// all possible callbacks
type Callback<T extends EventTypes> = (event: Events<T>) => void
// all possible subjects
type Subjects<T extends EventTypes> = Subject<Events<T>>
const collection = new Map<EventTypes, Subjects<EventTypes>>();
function fnWithConstraint<T extends EventTypes>(
name: T,
cb: Callback<T>
): Subscription | null
function fnWithConstraint(
name: EventTypes,
cb: Callback<EventTypes>
): Subscription | null {
const subject = collection.has(name)
? collection.get(name)
: new Subject<Events<EventTypes>>()
if (subject !== undefined) {
collection.set(name, subject);
return subject.subscribe(cb)
}
return null
}
fnWithConstraint("b", (event) => console.log(event));
在这种情况下,您应该放松一点。从外部来看,这个函数现在是安全的。
您的示例不起作用,因为您使用的是子类型 T
。它并不意味着 T
等于 a | b | c
它只是意味着 T
可能是这个联合的任何子类型。
考虑这个例子:
function fnWithConstraint<T extends EventTypes,>(
name: T,
cb: Callback<T>
): Subscription | null {
const subject = collection.has(name)
? collection.get(name)
: new Subject<Events<T>>()
if (subject !== undefined) {
collection.set(name, subject);
return subject.subscribe(cb)
}
return null
}
declare const a:'a' & {__tag:'Hello'}
fnWithConstraint(a, (event) => console.log(event));
event
参数是 Events<"a" & { __tag: 'Hello'; }>
.
虽然 TS 允许您使用 const a
,但使用 event
参数是不安全的,fnWithConstraint(a, (event) => console.log(event.type.__tag /** Hello */));
请记住,a
for typescript 是一个常规对象,可以有自己的子类型。看到这个:keyof 'a'
。超过 30 个属性。
TS 还将允许您将此品牌类型与我的解决方案一起使用,但 event
参数将是 valid/safe
can class methods be overloaded in the same way
是的。
简单示例:
class Foo {
run(arg: string): number
run(arg: number): string
run() {
return 'NOT IMPLEMENTED' as any
}
}
const result = new Foo();
result.run(42) // string
result.run('str') // number
我有一个模块可以让呼叫者订阅一组离散的事件。当调用者订阅给定事件时,他们提供事件名称作为第一个参数。从那个论点,我希望能够推断出回调签名。
该实现为每个受支持的事件使用 RxJS Subject
,并为每次调用 myModule.subscribe(eventType)
创建对其的订阅。下面显示了实施的精简版本(您还可以在 this TS Playground workspace 上看到这个 运行)
import { Subject, Subscription } from "rxjs";
const EventNames = {
a: "a",
b: "b",
c: "c",
} as const;
type Payloads = {
[EventNames.a]: string;
[EventNames.b]: number;
[EventNames.c]: boolean;
};
type EventTypes = keyof typeof EventNames;
// all possible event objects
type Events<T> = T extends EventTypes ? {type: T, payload: Payloads[T]} : never;
// all possible callbacks
type Callback<T> = T extends EventTypes ? (event: Events<T>) => void : never;
// all possible subjects
type Subjects<T> = T extends EventTypes ? Subject<Events<T>> : never;
const collection = new Map<EventTypes, Subjects<EventTypes>>();
function fnWithConstraint<T extends EventTypes>(
name: T,
cb: Callback<T>
): Subscription | null {
const subject = collection.has(name)
? collection.get(name)
: new Subject<Events<T>>();
if (subject !== undefined) {
collection.set(name, subject);
/* ^ Type '{ type: "a"; payload: string; }' is not assignable
to type 'Events<T>'.
*/
const subscription = subject.subscribe(cb)
/* ^ This expression is not callable. Each member of the union
type '{ (observer?: Partial<Observer<Events<T>>> | undefined):
Subscription; (next: (value: Events<T>) => void): Subscription;
... 1 more ...' has signatures, but none of those signatures
are compatible with each other.
*/
return subscription;
}
return null;
}
fnWithConstraint("b", (event) => console.log(event));
// expect typeof event -> { type: "b"; payload: number; }
我无法让它编译成功。我链接到的 TS Playground 显示了我在 line:45 上获得的正确结果,但编译器抱怨 Subjects 类型签名,我似乎无法解决它。我错过了什么?
你只需要重载你的函数:
import { Subject, Subscription } from "rxjs";
const EventNames = {
a: "a",
b: "b",
c: "c",
} as const;
type Payloads = {
[EventNames.a]: string;
[EventNames.b]: number;
[EventNames.c]: boolean;
};
type EventTypes = keyof typeof EventNames;
// all possible event objects
type Events<T extends EventTypes> = { type: T, payload: Payloads[T] }
// all possible callbacks
type Callback<T extends EventTypes> = (event: Events<T>) => void
// all possible subjects
type Subjects<T extends EventTypes> = Subject<Events<T>>
const collection = new Map<EventTypes, Subjects<EventTypes>>();
function fnWithConstraint<T extends EventTypes>(
name: T,
cb: Callback<T>
): Subscription | null
function fnWithConstraint(
name: EventTypes,
cb: Callback<EventTypes>
): Subscription | null {
const subject = collection.has(name)
? collection.get(name)
: new Subject<Events<EventTypes>>()
if (subject !== undefined) {
collection.set(name, subject);
return subject.subscribe(cb)
}
return null
}
fnWithConstraint("b", (event) => console.log(event));
在这种情况下,您应该放松一点。从外部来看,这个函数现在是安全的。
您的示例不起作用,因为您使用的是子类型 T
。它并不意味着 T
等于 a | b | c
它只是意味着 T
可能是这个联合的任何子类型。
考虑这个例子:
function fnWithConstraint<T extends EventTypes,>(
name: T,
cb: Callback<T>
): Subscription | null {
const subject = collection.has(name)
? collection.get(name)
: new Subject<Events<T>>()
if (subject !== undefined) {
collection.set(name, subject);
return subject.subscribe(cb)
}
return null
}
declare const a:'a' & {__tag:'Hello'}
fnWithConstraint(a, (event) => console.log(event));
event
参数是 Events<"a" & { __tag: 'Hello'; }>
.
虽然 TS 允许您使用 const a
,但使用 event
参数是不安全的,fnWithConstraint(a, (event) => console.log(event.type.__tag /** Hello */));
请记住,a
for typescript 是一个常规对象,可以有自己的子类型。看到这个:keyof 'a'
。超过 30 个属性。
TS 还将允许您将此品牌类型与我的解决方案一起使用,但 event
参数将是 valid/safe
can class methods be overloaded in the same way
是的。
简单示例:
class Foo {
run(arg: string): number
run(arg: number): string
run() {
return 'NOT IMPLEMENTED' as any
}
}
const result = new Foo();
result.run(42) // string
result.run('str') // number