交集 'xxx & xxx' 被缩减为 'never' 因为 属性 'xxx' 在某些成分中有冲突的类型
The intersection 'xxx & xxx' was reduced to 'never' because property 'xxx' has conflicting types in some constituents
我在使用 ts 泛型时出错,这是简单的代码:
最后一行ts报如下错误:
error TS2345: Argument of type 'Task<"build"> | Task<"repair">' is not assignable to parameter of type 'never'.
The intersection 'Task<"build"> & Task<"repair">' was reduced to 'never' because property 'type' has conflicting types in some constituents.
44 action_map[task.type].execute(task);
我尝试使用 switch 来避免错误:
function execute<T extends TaskType>(task: Task<T>) {
switch (task.type) {
case "build":
// now type of `task` should be `Task<"build">`
BuildAction.execute(task);
break;
case "repair":
// now type of `task` should be `Task<"repair">`
RepairAction.execute(task);
break;
default:
// now type of `task` should be `Task<never>`
console.log("Error");
}
}
但更糟的是:
error TS2345: Argument of type 'Task<T>' is not assignable to parameter of type 'Task<"build">'.
Type 'T' is not assignable to type '"build"'.
Type 'keyof TMap' is not assignable to type '"build"'.
Type '"repair"' is not assignable to type '"build"'.
50 BuildAction.execute(task);
我注意到 vscode 对 task
的类型提示总是 Task<T>
而不是我的预期。
那么,我该怎么办?
您面临的问题是编译器没有直接支持处理相关联合类型,如microsoft/TypeScript#30581.[=62 中所述=]
您的 task
属于 union type:
declare var task: Task<"build"> | Task<"repair">;
所以 task
要么是 Task<"build">
要么是 Task<"repair">
。因此 action_map[task.type].execute
也是联合类型:
const execute = action_map[task.type].execute;
// const execute: ((task: Task<"build">) => void) | ((task: Task<"repair">) => void)
这意味着 execute
要么接受 Task<"build">
,要么接受 Task<"repair">
,但不会同时接受两者。假设我们有另一个任务:
declare var otherTask: Task<"build"> | Task<"repair">;
显然使用 execute
执行该任务是不安全的:
execute(otherTask); // error!
毕竟,otherTask
可能是 Task<"build">
,而 execute
可能是 ((task: Task<"repair">) => void)
。好吧,不幸的是,编译器可以使用这些类型来确定某物是否安全。由于 execute(otherTask)
是不安全的,并且 task
与 otherTask
具有相同的联合类型,因此 execute(task)
被视为不安全的原因相同:
execute(task); // same error!
当然我们知道execute
会接受task
,因为它们彼此相关。但那是因为我们正在跟踪 task
变量的身份,而不仅仅是类型。编译器无法区分 execute(otherTask)
和 execute(task)
之间的区别。所以它担心它们可能不相关。
错误提到交集的原因是因为调用函数联合的唯一安全方法是 call it with an intersection of the parameters. If execute
accepts a Task<"build">
or a Task<"repair">
but we don't know which, the only safe thing to pass it would be something that's both a Task<"build">
and a Task<"repair">
... hence a Task<"build"> & Task<"repair">
. And of course such a thing is impossible; they have conflicting properties, so you get that message about the never
type.
这就是您看到错误的原因。
至于修复它,TypeScript 4.6 引入了一些 improvements specifically to address microsoft/TypeScript#30581; you can read about it in microsoft/TypeScript#47109。
推荐的方法是创建一个 generic 函数。如果 K extends TaskType
是泛型类型参数,编译器 将 允许您使用类型 Task<K>
的参数调用类型 (task: Task<K>)=>void
的函数。 TypeScript 4.6 的改进意味着编译器会将 action_map[task.type]
视为 (task: Task<K>)=>void
:
类型
type SomeTask<T extends TaskType = TaskType> = { [K in T]: Task<K> }[T];
function genericExecute<K extends TaskType>(task: SomeTask<K>) {
action_map[task.type].execute(task); // okay
}
编译器不需要 SomeTask<T>
类型,但它显示了如何轻松获得任务类型的联合。如果 TaskType
包含更多联合成员,SomeTask
也会获得这些成员。 genericExecute()
的 body 被认为是安全的。
幸运的是,您可以毫无问题地拨打 genericExecute(task)
:
genericExecute(task); // okay
因此,这是针对此类相关联合类型的推荐方法。
我在使用 ts 泛型时出错,这是简单的代码:
最后一行ts报如下错误:
error TS2345: Argument of type 'Task<"build"> | Task<"repair">' is not assignable to parameter of type 'never'.
The intersection 'Task<"build"> & Task<"repair">' was reduced to 'never' because property 'type' has conflicting types in some constituents.
44 action_map[task.type].execute(task);
我尝试使用 switch 来避免错误:
function execute<T extends TaskType>(task: Task<T>) {
switch (task.type) {
case "build":
// now type of `task` should be `Task<"build">`
BuildAction.execute(task);
break;
case "repair":
// now type of `task` should be `Task<"repair">`
RepairAction.execute(task);
break;
default:
// now type of `task` should be `Task<never>`
console.log("Error");
}
}
但更糟的是:
error TS2345: Argument of type 'Task<T>' is not assignable to parameter of type 'Task<"build">'.
Type 'T' is not assignable to type '"build"'.
Type 'keyof TMap' is not assignable to type '"build"'.
Type '"repair"' is not assignable to type '"build"'.
50 BuildAction.execute(task);
我注意到 vscode 对 task
的类型提示总是 Task<T>
而不是我的预期。
那么,我该怎么办?
您面临的问题是编译器没有直接支持处理相关联合类型,如microsoft/TypeScript#30581.[=62 中所述=]
您的 task
属于 union type:
declare var task: Task<"build"> | Task<"repair">;
所以 task
要么是 Task<"build">
要么是 Task<"repair">
。因此 action_map[task.type].execute
也是联合类型:
const execute = action_map[task.type].execute;
// const execute: ((task: Task<"build">) => void) | ((task: Task<"repair">) => void)
这意味着 execute
要么接受 Task<"build">
,要么接受 Task<"repair">
,但不会同时接受两者。假设我们有另一个任务:
declare var otherTask: Task<"build"> | Task<"repair">;
显然使用 execute
执行该任务是不安全的:
execute(otherTask); // error!
毕竟,otherTask
可能是 Task<"build">
,而 execute
可能是 ((task: Task<"repair">) => void)
。好吧,不幸的是,编译器可以使用这些类型来确定某物是否安全。由于 execute(otherTask)
是不安全的,并且 task
与 otherTask
具有相同的联合类型,因此 execute(task)
被视为不安全的原因相同:
execute(task); // same error!
当然我们知道execute
会接受task
,因为它们彼此相关。但那是因为我们正在跟踪 task
变量的身份,而不仅仅是类型。编译器无法区分 execute(otherTask)
和 execute(task)
之间的区别。所以它担心它们可能不相关。
错误提到交集的原因是因为调用函数联合的唯一安全方法是 call it with an intersection of the parameters. If execute
accepts a Task<"build">
or a Task<"repair">
but we don't know which, the only safe thing to pass it would be something that's both a Task<"build">
and a Task<"repair">
... hence a Task<"build"> & Task<"repair">
. And of course such a thing is impossible; they have conflicting properties, so you get that message about the never
type.
这就是您看到错误的原因。
至于修复它,TypeScript 4.6 引入了一些 improvements specifically to address microsoft/TypeScript#30581; you can read about it in microsoft/TypeScript#47109。
推荐的方法是创建一个 generic 函数。如果 K extends TaskType
是泛型类型参数,编译器 将 允许您使用类型 Task<K>
的参数调用类型 (task: Task<K>)=>void
的函数。 TypeScript 4.6 的改进意味着编译器会将 action_map[task.type]
视为 (task: Task<K>)=>void
:
type SomeTask<T extends TaskType = TaskType> = { [K in T]: Task<K> }[T];
function genericExecute<K extends TaskType>(task: SomeTask<K>) {
action_map[task.type].execute(task); // okay
}
编译器不需要 SomeTask<T>
类型,但它显示了如何轻松获得任务类型的联合。如果 TaskType
包含更多联合成员,SomeTask
也会获得这些成员。 genericExecute()
的 body 被认为是安全的。
幸运的是,您可以毫无问题地拨打 genericExecute(task)
:
genericExecute(task); // okay
因此,这是针对此类相关联合类型的推荐方法。