这些是我的类型:
export type Mapping =
| {
type: 'ok';
data: Record<string, string>;
}
| {
type: 'error';
data: Error;
};
export type Payload<T> = Extract<Mapping, { type: T }>['data'];
export interface Callback<T> {
(data: Payload<T>): void;
}
export type Store = {
[K in Mapping['type']]: Set<Callback<K>>;
};
这是我的代码:
const storage: Store = {
ok: new Set(),
error: new Set(),
};
function store<T extends Mapping['type']>(
type: T,
callback: Callback<T>
): void {
storage[type].add(callback);
}
我得到的错误:
Argument of type 'Callback<T>' is not assignable to parameter of type 'Callback<"ok"> & Callback<"error">'.
Type 'Callback<T>' is not assignable to type 'Callback<"ok">'.
Types of parameters 'data' and 'data' are incompatible.
Type 'Record<string, string>' is not assignable to type 'Payload<T>'.
Type 'Record<string, string>' is not assignable to type 'Extract<{ type: "error"; data: Error; }, { type: T; }>["data"]'.
Type 'Record<string, string>' is missing the following properties from type 'Error': name, messagets(2345)
我想实现,如果某个回调存储在 storage.ok
中或storage.error
可以处理正确的参数类型。所以我尝试使用条件类型,轻松地为回调参数类型添加更多选项。我认为错误在 'Callback<"ok"> & Callback<"error">'
因为它不应该是&
。但错误在哪里呢? storage
有正确的类型:
type Store = {
error: Set<Callback<"error">>;
ok: Set<Callback<"ok">>;
}
编辑:问题来自.add
功能:
(method) Set<T>.add(value: Callback<"ok"> & Callback<"error">): Set<Callback<"ok">> | Set<Callback<"error">>
哪里来的&
来自?
最佳答案
这是编译器无法执行必要的高阶类型分析来验证您在store()
的实现中所做的事情。是安全的。
让我们定义PickStore<T>
,像这样:
type PickStore<T extends Mapping['type']> = { [K in T]: Set<Callback<K>> };
这本质上与 Pick<Store, T>
相同因此是 Store
的父类(super class)型,但编译器无法进行高阶类型分析来验证这一点。对于 T
的任何特定值,编译器可以看到PickStore<T>
是 Store
的父类(super class)型:
const sOk: PickStore<"ok"> = storage; // okay
const sError: PickStore<"error"> = storage; // okay
const sBoth: PickStore<"ok" | "error"> = storage; // okay
const sNeither: PickStore<never> = storage; // okay
但是当您尝试对通用执行相同操作时 T
,编译器犹豫不决:
function test<T extends Mapping['type']>() {
const sT: PickStore<T> = storage; // error!
// Type 'Store' is not assignable to type 'PickStore<T>'
}
这就是你的问题。如果你能扩大storage
至PickStore<T>
store
里面函数,编译器将能够看到 storage[type]
类型为Set<Callback<T>>
,因此您可以 add()
callback
参数没有问题。
相反,编译器只理解 storage[type]
可分配给联盟 Set<Callback<"ok">> | Set<Callback<"error">
。这是事实,但对于您的目的来说不够具体。 storage[type]
之间的相关和callback
丢失了。 (有关编译器无法跟踪多个值之间类型相关性的问题,请参阅 microsoft/TypeScript#30581。)因此 add()
方法被视为两个函数类型的联合。并且,当您 call a union of function types ,编译器需要其参数的交集,以保证类型安全。 (如果您知道您有一个函数是 X
-taker 或 Y
-taker,但您不确定是哪一个,那么为了安全起见,您最好给它一个 X & Y
。)
我建议,在编译器无法验证某些高阶类型分析的情况下,您可以使用 type assertion只是告诉编译器你正在做的事情是安全的。这将保证安全的负担从编译器转移到了您身上,因此您应该小心。无论如何,它可能看起来像这样:
function store<T extends Mapping['type']>(
type: T,
callback: Callback<T>
): void {
(storage as any as PickStore<T>)[type].add(callback);
}
在这里,我们刚刚向编译器断言 storage
可以被视为 PickStore<T>
(我们需要通过 unknown
或 any
进行中间断言,因为编译器确实无法处理这种关系)。过了这个障碍,生产线的其余部分就顺利通过了。
关于typescript - 回调参数的条件类型,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65368468/