Description
Background
Something that newcomers run into as a result of migrating to TypeScript is that certain patterns don't work quite as easily anymore. For example, let's consider the following example:
export const Foo = props => <div>{props.name}</div>
Foo.propTypes = {
// ...
};
This example doesn't work in TypeScript because propTypes
isn't a valid property on Foo
's type.
In the above case, there are two potential workarounds:
-
Find/declare a type that exists that is callable and has a valid property:
export const Foo: StatelessComponent<any> = props => <div> {props.name} </div> Foo.propTypes = { // ... };
This option has the benefit of being able to statically catch incorrectly named declarations, but is somewhat unattractive because of the placement. It also doesn't work well if the original declaration was a function declaration like so:
export function Foo(props) { return <div>props.name</div> }
Because it would need to be rewritten to a variable declaration to allow an annotation.
-
Rewrite the
const
declaration to a function declaration and use namespace merging:export function Foo(props) { return <div>props.name</div> } namespace Foo { export let propTypes = { /*...*/ }; }
This option takes advantage of the
namespace
construct in TypeScript which performs value-merging; however, it is potentially worse because- It requires the use of a construct that is fairly TypeScript specific, which may be unattractive to newcomers.
- It only works for
function
declarations. This requires a rewrite from anyconst
declarations.
Unless there's a new construct which creates a callable object (cc @bterlson), then I want to discuss the option of recognizing property assignments on functions. In other words, the original example would simply be something that TypeScript could understand.
Proposal
For an effective function declaration named F
at the top-level scope, the first assignment to a property p
on F
(i.e. F.p
) is considered a declaration of a property named p
on the type of F
.
An effective function declaration can be described by a FunctionDeclaration or a VariableDeclaration whose initializer is
- a FunctionExpression
- an ArrowFunction
- a series of arbitrarily nested ParenthesiezdExpressions whose innermost expression is either of the above
Precedent
We already do this to some extent in checkJs
mode because we simply have to. Not doing this would lead to an unacceptable experience there.
Drawbacks
- These declarations are significantly less explicit than other declarations, which makes the language less consisent.
- Type annotations cannot be placed on these declaration sites, whereas all other variable/property declarations allow a type annotation.
- Users may be confused by the positions where these declarations can occur.
Alternative Ideas
-
Augmented variable declaration syntax
let Foo.propTypes = { /*...*/ };
-
Allow namespaces to merge with
const
/let
/var
declarations.export const Foo = props => <div>{props.name}</div>; export namespace Foo { export let propTypes = { /*...*/ }; }
-
Find a way to cleanly coerce the type of a function declaration.