What's the difference between declaring functions using the fat arrow and non-fat arrow syntax in interfaces and types in TypeScript?
For example:
build(paramOne: string): string;
Compared to:
build: (paramOne: string) => string;
At first, I thought it would restrict the way I implement the functions but it doesn't seem like the case. So, I don't think it has to do with the this
like in ES6. But I do notice that the one declared in fat arrow syntax has issues when I attempt to overload.
For example, this would not be acceptable:
build: (paramOne: string) => void
build: (paramOne: number, paramTwo: string) => void
This would give an error:
Subsequent property declarations must have the same type. Property 'build' must be of type '{ (paramOne: string): string; (paramOne: number, paramTwo: string): number; }', but here has type '(params: unknown) => void
But this is OK:
build(paramOne: string): string;
build(paramOne: number, paramTwo: string): number;
So, are the 2 syntaxes the same? Are there any differences or scenarios I should use one over another?
The main difference is that the one with arrow syntax is a function type expression, while the other is a call signature. The obvious difference is the syntax, from the handbook:
Note that the syntax is slightly different compared to a function type expression - use : between the parameter list and the return type rather than =>.
You are correct that this syntax does not make the this
of the function be lexically scoped. You can specify any this
type you expect the function to have when called:
thisChanged: (this: { test: number }, param1: number, param2: boolean) => string;
But as you also correctly guessed, there are subtle differences in application. Note the term expressions as opposed to signatures. The former "evaluates" to one call signature unless defined as an intersection of expressions, while the latter can be defined multiple times to create function overloads:
//multiple call signatures used for function overloading:
function build(param1: string) : string;
function build(param1: number) : string;
function build(param1: string | number) {
return param1.toString();
}
//intersection of expressions
type build2 = ((param1:string) => string) & ((param1:number) => string);
declare const b: build2;
b(2) //OK
b("test") //OK
b(false) //No overload matches this call.
Applied to your use case of using them in object types, call signatures can be specified multiple times under the same key because this results in multiple function overloads. On the other hand, [key]: <function expression>
syntax defines a property with a value of a function expression type (hence the inability to specify the property multiple times as keys must be unique).
Also note that the inferred types correspond to how the functions are defined (as function declarations or expressions):
//call signature: function build3(param1: string | number): string
function build3(param1: string|number) : string {
return param1.toString();
}
//function expression: const build4: (param1: string | number) => string
const build4 = (param1: string|number) : string => param1.toString();