typescript - 回调参数的条件类型
问题描述
这些是我的类型:
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
,但编译器无法进行高阶类型分析来验证. 对于 的任何特定值T
,编译器可以看到它PickStore<T>
是 的超类型Store
:
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
但是,当您尝试对generic 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]
是 type Set<Callback<T>>
,因此您可以毫无问题地add()
使用callback
参数。
相反,编译器只知道storage[type]
可以分配给union Set<Callback<"ok">> | Set<Callback<"error">
。这是真的,但对于您的目的来说不够具体。和之间的相关性丢失。(有关编译器无法跟踪多个值之间类型相关性的问题,请参阅microsoft/TypeScript#30581 。)因此,该方法被视为两种函数类型的联合。而且,当您调用函数类型的联合时,编译器需要其参数的交集,以保证类型安全。(如果你知道你有一个函数是-taker 或storage[type]
callback
add()
X
Y
-taker 但你不确定哪个,你最好给它一个X & Y
安全的。)
我建议,在编译器无法验证某些高阶类型分析的这种情况下,您可以使用类型断言来告诉编译器您正在做的事情是安全的。这将保证安全的负担从编译器转移到了你身上,所以你应该小心。无论如何,它可能看起来像这样:
function store<T extends Mapping['type']>(
type: T,
callback: Callback<T>
): void {
(storage as any as PickStore<T>)[type].add(callback);
}
在这里,我们刚刚对编译器进行了断言,storage
可以将其视为 a (我们需要通过orPickStore<T>
的中间断言,因为编译器确实存在关系问题)。过了那个障碍,线路的其余部分顺利通过。unknown
any
推荐阅读
- ruby - 避免在 Activerecord 上进行多选
- c++ - 返回 make_shared
,继承和转换到 Derived 没有按预期工作? - php - PHP 保护 SQL 登录数据
- sql-server - 将多个动态透视 SQL 查询结果导出到 Excel
- reactjs - 错误:React Hook useEffect 缺少依赖项:'dispatch'。包括它或删除依赖数组
- android - 引起:java.lang.IllegalStateException:预期 BEGIN_OBJECT 但在第 1 行第 1 列路径 $
- python - 如何绘制对应于最高值的直线?
- javascript - 根据谷歌表格文档中的关键字抓取谷歌搜索句子
- sql - INSERT A Record when clicking a button depending on a if statement
- android - android remove category.BROWSABLE from
?