What's the difference between these two ways of declaring arrow function in typescript?
type MyState = {
required: boolean;
optional?: boolean;
};
const myFn1: (s: MyState) => MyState = (s) => ({...s, unexpected: 1}) // no error
const myFn2 = (s: MyState): MyState => ({...s, unexpected: 1}) // error
myFn2 has the following error
Type '{ unexpected: number; required: boolean; optional?: boolean | undefined; }' is not assignable to type 'MyState'.
Object literal may only specify known properties, and 'unexpected' does not exist in type 'MyState'.
Why doesn't myFn1 have the same error?
Additionally, if I want this line
const myFn3: FnType = (s) => ({...s, unexpected: 1})
to give same error as myFn2
, what should FnType
be?
CodePudding user response:
An sub-type should be assignable to its base-type. So from a type theory point of view { required: boolean; optional?: boolean; unexpected: number }
should be assignable to MyState
. When looking at an object type you should never assume a value that satisfies this this has ONLY those properties, just that it must have the specified by the type.
The exception to this is what is called 'Excess property checks'. This is where TypeScript will check if you have extra properties if you are assigning an object literal to something that is typed as a specific object type. This is a pragmatic check done to avoid a certain class of errors.
So let look at the two examples:
const myFn2 = (s: MyState): MyState => ({...s, unexpected: 1})
Here you are returning an object literal in a function that has a return type of MyState
, so excess property checks kicks in.
const myFn1: (s: MyState) => MyState = (s) => ({...s, unexpected: 1})
Here you have a function expression assigned to a variable that is a function type. TypeScript will type the function expression first, then it will check if it is assignable to the function type. And it is. The subtype returned by the function expression is assignable to the return type of the function type. There is no direct assignment of an object literal to something that has a definite type.
You could argue that TypeScript should use the same mechanism it uses for parameters types (contextual typing) to get the return type and perform excess property checks on that. But that is just not the way typescript works at the moment. (I'm sure there is a discussion on github about this)
There is no way to generally avoid this. You could add properties you specifically don't want to MyState
(and make them optional and type them as udnefined
) but this isn't a scalable solution.
The only real solution is to add the explicit return type to the function. But generally don't depend on object types only having properties defined by the type.