Reputation: 33
I created two code examples. The only difference between them is what expression I pass to the switch operator.
In a first case I use an object property. And it works fine.
In a second case I create a type
variable. And Typescript throw an error message:
Property 'name' does not exist on type 'Action'.
Property 'name' does not exist on type '{ type: "reset"; }'.
Why is this happening?
The object property action.type
and variable type
are of the same 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();
}
}
Upvotes: 3
Views: 1077
Reputation: 51162
Basically, Typescript doesn't keep track of a relationship between the types of the variables action
and type
; when type
's type is narrowed (such as in a case
of a switch
statement), it doesn't also narrow action
's type.
On the assignment const { type } = action;
, the compiler infers type: Action['type']
, which happens to be 'reset' | 'update'
. Later, the case
expression doesn't narrow the type of action
because no type-guarding check was done on action
.
For this to behave the way you want it to behave, the compiler would have to introduce a type variable T extends Action['type']
and infer type: T
, while simultaneously narrowing action
to the type : Action & { type: T }
. Then when type
's type is narrowed, T
itself would have to be narrowed, so the effect would propagate to action
's type which would involve T
.
Introducing a new type variable like this on every variable assignment, and control-flow narrowing the upper bounds of type variables, would greatly complicate the type-checking algorithm. It would also greatly complicate the inferred types making them harder for users to understand; so it's reasonable that Typescript doesn't do this. Generally speaking, type-checkers don't prove every provable property of your code, and this is an example.
Upvotes: 4
Reputation: 11577
When you refer to the argument it can infer the type from the whole object but when you create a constant you limit the type to simply become "reset" | "update"
and the other bits of Action object type info is lost.
Upvotes: 1