我创建了两个代码示例。它们之间的唯一区别是我传递给switch运算符的表达式。
在第一种情况下,我使用对象属性。而且效果很好。
在第二种情况下,我创建了一个type
变量。然后Typescript抛出错误消息:
“操作”类型上不存在属性“名称”。
属性'name'在类型'{不存在,类型:“ reset”; }'。
为什么会这样?
对象属性action.type
和变量type
具有相同的类型'reset' | 'update'
。
interface State {
name: string;
cars: any[];
}
type Action = { type: 'reset' } | { type: 'update', name: string };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'update':
return { ...state, name: action.name };
case 'reset':
return {...state, cars: [] };
default:
throw new Error();
}
}
interface State {
name: string;
cars: any[];
}
type Action = { type: 'reset' } | { type: 'update', name: string };
function reducer(state: State, action: Action): State {
/**
* Create a 'type' variable
*/
const { type } = action;
switch (type) {
case 'update':
return { ...state, name: action.name };
/**
* Typescript will throw an error message
* Property 'name' does not exist on type 'Action'.
* Property 'name' does not exist on type '{ type: "reset"; }'.
*/
case 'reset':
return {...state, cars: [] };
default:
throw new Error();
}
}
答案 0 :(得分:2)
基本上,Typescript不会跟踪变量action
和type
的类型之间的关系;当type
的类型变窄时(例如在case
语句的switch
中),它也不会变窄action
的类型。
在分配const { type } = action;
上,编译器推断type: Action['type']
,恰好是'reset' | 'update'
。后来,case
表达式不会缩小action
的类型,因为没有对action
进行类型保护检查。
要使此行为按照您希望的方式运行,编译器将必须引入类型变量T extends Action['type']
并推断type: T
,同时将action
缩小为类型{{ 1}}。然后,当: Action & { type: T }
的类型变窄时,type
本身必须变窄,因此效果将传播到T
的类型,其中涉及action
。>
在每个变量分配中引入像这样的新类型变量,并且控制流会缩小类型变量的上限,这将极大地使类型检查算法复杂化。这也将使推断出的类型变得非常复杂,使用户难以理解它们。因此Typescript不执行此操作是合理的。一般来说,类型检查器不会证明代码的每个可证明属性,这是一个示例。
答案 1 :(得分:1)
当您引用参数时,它可以从整个对象中推断出类型,但是当您创建一个常量时,您将类型限制为简单地变成"reset" | "update"
,而Action对象类型信息的其他位将丢失。