Why does TypeScript track mutation of function static properties?
I always thought that TypeScript does not track object mutations. For example:
type DescribableObject = {
name: string;
age: number;
};
// error
const obj: DescribableObject = {
name: 'sdf'
}
obj.age = 2
But, it seems that in some circumstances it tracks mutation of function static properties.
type DescribableFunction = {
description: string;
(): boolean;
};
// error
const fn: DescribableFunction = () => true
//fn.description = 'hello';
If you uncomment //fn.description = 'hello';
, the TypeScript error will disappear.
Furthermore, if you hover over fn
you will see that TS treats fn
as some kind of module
.
What kind of module is the fn
function?
Is this behaviour documented?
Solution 1:
Starting with TypeScript 3.1, you are allowed to define "expando" properties on functions. This was implemented in microsoft/TypeScript#26368 as a fix to microsoft/TypeScript#15868. Apparently there are common patterns where developers add properties to functions in JavaScript, but before this feature there was no idiomatic way to do this in TypeScript.
Right now you can do this:
function foo(x: string) { foo.callCount++; return x.length }
foo.callCount = 0;
But before TypeScript 3.1 you had to do something like this:
function bar(x: string) { bar.callCount++; return x.length }
namespace bar {
export var callCount = 0;
}
or this:
const baz = Object.assign(
(x: string) => { baz.callCount++; return x.length },
{ callCount: 0 }
);
or even this:
const qux = ((x: string) => { qux.callCount++; return x.length }) as
{ (x: string): number; callCount: number };
qux.callCount = 0;
all of which were fairly ugly.
While it might be nice to allow expando properties on all objects (like DescribableObject
) and not just functions, as requested in microsoft/TypeScript#12416, it seems that this is not as common or important a pattern to support. Generally speaking you can use an object literal to add all the properties you want at once (although sometimes dependencies can be difficult to deal with) but you can't do that with a function.
Playground link to code