Open mbrowne opened 6 years ago
I just was thinking about this. I would like to make a proposal to type functions declarations, by adding to the function keyword a generic param that can shape the function.
function<T extends (...V): R> // Maybe something like this
To be used like this:
function<ActionReducer<State>> reducer (state, action) {
state // Type infers to State.
action // Type infers to Action, as is its default value.
return // Type infers to State.
}
Variadic types are currently being tracked at #5453, but maybe this isn't need for this to work, and this can be implemented somehow with the current union types, or the same way interfaces like this are being currently declared, but duplicating the amount of interfaces a having a any default to the nth
interface.
Another proposal, should be supporting @type
property of JSDocs. With this, we can be able to type functions.
/**
* @type {ActionReducer<State, ActionType>}
*/
function reducer(state, action) {
state // Type infers to State.
action // Type infers to ActionType, as is its default value.
return // Type infers to State.
}
Just curious if anyone on the TypeScript team is interested in working on this feature for some later release of 3.x? Where would this fit into the roadmap?
Something like this would be amazing:
async function user: Resolvers.TQueryUser (_, { id }, context) {
const user = await context.prisma.users.findOne({ where: { id: +id } });
return user;
}
My current alternative is to do:
const user: Resolvers.TQueryUser = async function user(_, { id }, context) {
const user = await context.prisma.users.findOne({ where: { id: +id } });
return user;
}
But unfortunately, I wouldn't be able to type them, in say, within objects. E.g.:
{
user: user: Resolvers.TQueryUser = async function user(_, { id }, context) {
const user = await context.prisma.users.findOne({ where: { id: +id } });
return user;
}
}
Or:
{
user: async function user: Resolvers.TQueryUser (_, { id }, context) {
const user = await context.prisma.users.findOne({ where: { id: +id } });
return user;
}
}
I could also do:
interface IUserResolverMap {
// Queries
Query: {
user: Beast.TQueryUser
users: Beast.TQueryUsers
me: Beast.TQueryUser
}
// Mutations
Mutation: {
newUser: Beast.TMutationNewUser
authenticate: Beast.TQueryAuthenticate
}
}
const resolvers: IUserResolverMap = {
Query: {
async user(_, { id }, context) {
const user = await context.prisma.users.findOne({ where: { id: +id } });
return user;
}
}
...
}
But then I would get this complaint (some might not depending on your ESLint):
Missing return type on function.eslint(@typescript-eslint/explicit-function-return-type)
I realize this issue has been around for some time, but I just wanted to say: I still want this. When new to typescript, I had originally tried to write e.g.
type MyFn = (a: number, b: string) => number
declare function h: MyFn
function h(a, b) {
return a
}
and was very disappointed when I found out you couldn't do this.
I just want to add that I personally would be happy with a much less slick syntax than the other commenters seem to be proposing. For example declare function f: T
would be just fine. In addition to the other benefits proposed by the other commenters, this can be a great way to make your code easier to read since when I see
function g(a: number, b: string): number {
return a
}
I'm trying to read the names of the parameters and follow then throught the function body, and the less busy the signature line is, the easier this is. For example, when I see
let e: (a: number, b: string) => number
e = (a, b) => {
a
}
(NOTE: This is not what I actually want to read or write)
I can mentally separate the process of determining the types from following the variable names throught the function body. Although it's not obvious in this contrived example, I often read code (especially difficult-to-understand code) by going back and forth visually between the argument list and the function body to try to figure out what's going on. And being able to look up the types of the argument names separately from referring to the names themselves makes this easier.
+1 on this
This is really the most painful thing on Typescript right now (because everything else is quite perfect <3) Especially when dealing with reusable express middleware, this is quickly getting out of hand, even a basic function declaration takes > 5 lines. I would love some comments from maintainer (cc @RyanCavanaugh I know it's not on the roadmap for the moment)
// this simple js function
function myMiddleware(req, res, next);
// ... can become this monstrosity
function myMiddleware(
req: express.request<
MyParams,
MyBody
MyQueries
>,
res: express.response<MyBody>,
next: express.nextFunction
): Promise<void>;
Using const and anonymous function can "help" but you will reach 80colums quickly anyway, change the behavior (function and const has slight difference), and it still not as cool as having a proper way of doing it.
const middleware: Custom<MyParams, MyBody, MyQueries> = function(
req,
res,
next
);
This could be something like this:
function: Custom<MyParams, MyBody, MyQueries> myMiddleware(req, res, next) ;
Note this is similar request as #39623
I agree with this issue. Function declarations are not the same as function expressions and arrow functions are not the same as normal functions. I would like to keep using function
as the default and only use () =>
when this
is required from the definition context like with callbacks fron instance.
In my opinion it makes the code a lot easier and faster to read because function doSomething (param) {}
looks like one token plus name when skimming over the code and const doSomething = param => {}
looks like 2 tokens plus name.
current form in TypeScript React:
const ComponentName: React.FC<{}> = props => {}
// would be sort of equivalent to:
function ComponentName (props: PropsWithChildren<{}>): React.ReactNode {
this.propTypes: WeakValidationMap<P> = null
this.contextTypes: ValidationMap<any> = undefined
this.defaultProps: Partial<P> = undefined
this.displayName: string = ''
}
So it would be really nice if we could do:
function ComponentName: React.FC<{}> (props) {}
What's so special about function
vs var
, let
or const
?
If the function type annotation appears before the generic parameters, how will it be able to reference those parameters?
Say we have a generic function type:
type ProcessingFunction<DocT extends Document, DataT> = (doc: DocT, data: DataT) => void;
and we want to apply it to an instance:
function process<DType>(doc: Document, data: DType): void {
...
}
This instance can be called with different generics:
process<{url: string, id: number, text: string}>(document, {url: '//example.com', id: 42, text: 'lorem ipsum…'});
process<{title: string, items: string[]}>(document, {title: 'Hello World', items: ['one', 'two']});
How would the function be annotated?
function: ProcessingFunction<Document, DType> process<DType>(doc: Document, data: DType): void {
// ^ Error: Cannot find name 'DType'
...
}
Note that as pointed out by someone else on Twitter, this problem already exists in the form of variable declaration:
let process: ProcessingFunction<Document, DType> = function <DType>(doc: Document, data: DType): void {
// ^ Error: Cannot find name 'DType'
...
};
But I guess my question still stands of if/how the issue will be addressed. It seems to me that this feature — annotating function declarations — should be able to handle generics.
@chharvey The purpose of this proposal is to have syntax for function declarations (function ...
) that's semantically equivalent to what you can do with function expressions (const myFunc ...
). If you see issues with specifying types of function expressions, then I suggest you propose a solution for that first (or create a new issue about it), since that's the syntax that already exists in TypeScript. Then I'm sure some semantically equivalent syntax could be designed for function declarations.
If you already have an idea of what the syntax should look like for either declarations or expressions to support the use cases you mentioned, feel free to share so that those considering this proposal can be aware of it when designing the syntax.
@mbrowne A few ideas:
Type annotation after the function name and generic parameters (if any), followed by an equals sign:
function process: ProcessingFunction = (doc: Document, data: unknown): void {
...
}
function process<DType>: ProcessingFunction<Document, DType> = (doc: Document, data: DType): void {
...
}
Con: A bit too much like variable declaration; leads people to believe they can remove the type annotations and be left with function process = (doc, data) {...}
, which is invalid.
Similar to 1 but with as
instead of colon and no equals sign:
function process as ProcessingFunction(doc: Document, data: unknown): void {
...
}
function process<DType> as ProcessingFunction<Document, DType>(doc: Document, data: DType): void {
...
}
Con: Type annotation too close to parameter list makes it look like function name / generic parameter list.
Type annotation in colon delimiters:
function process :ProcessingFunction: (doc: Document, data: unknown): void {
...
}
function process<DType> :ProcessingFunction<Document, DType>: (doc: Document, data: DType): void {
...
}
Con: Too unfamiliar; colon delimiters are found nowhere else in the language.
implements
after return type
function process(doc: Document, data: unknown): void implements ProcessingFunction {
...
}
function process<DType>(doc: Document, data: DType): void implements ProcessingFunction<Document, DType> {
...
}
Con: void implements ProcessingFunction
seems like a type per se.
Type annotation after the entire function body (following colon or as
).
function process(doc: Document, data: unknown): void {
...
}: ProcessingFunction;
function process<DType>(doc: Document, data: DType): void {
...
}: ProcessingFunction<Document, DType>;
function process(doc: Document, data: unknown): void {
...
} as ProcessingFunction;
function process<DType>(doc: Document, data: DType): void {
...
} as ProcessingFunction<Document, DType>;
Con: Big functions would have the annotation too far down from the head.
I think this was one of the first issues I ever comment on Github, and we still don't have a way to correctly type function expressions. But now, I think I can see the problems this could bring. As I far as I understand TS, it resolves generics the same way JS resolves const
variables, they need to be declared first before being used. So, how could you type something like:
interface FC<Params> {
(params: Params): void;
}
interface FooParams<T> {
bar: string;
baz: number;
data: T;
}
// ▼ A ▼ B ▼ C
const FooBazComponent: FC<FooParams<T>> = <T>({ data }) => {
// ..
};
// Errors:
// A: Error: Exported variable 'FooBazComponent' has or is using private name 'T'.(4025)
// B: 'T' is declared but its value is never read.(6133)
// C: Binding element 'data' implicitly has an 'any' type.(7031)
You see from the example that you can't, even without React, type a function declaration with a generic, because it will throw when it doesn't find the type name (Because they are scoped to the function where they are being declared).
Because of this, most of the proposal here are syntax won't work with generics, and I think that could be the reason behind why is this taking so long.
Consider the example I first use:
interface ActionReducer<Params> {
(params: Params): void;
}
interface State<T> {
bar: string;
baz: number;
data: T;
}
function<ActionReducer<State<T>>> reducer <T>({ data }, action) {
state // Type infers to State.
action // Type infers to Action, as is its default value.
return // Type infers to State.
}
// Errors:
// A: Error: Exported variable 'reducer' has or is using private name 'T'.(4025)
// B: 'T' is declared but its value is never read.(6133)
// C: Binding element 'data' implicitly has an 'any' type.(7031)
So, we would have the same problems.
However, @chharvey put some good examples when he puts the generics first. Even when all of them have some caveats when you want to actually use them, if we mix them, we may have something that could really be usable.
Keep in mind that TS type anotation should be easily removed, and should not produce invalid JS code when this is done. (This is a feature that Babel use, for example).
Type anotation with a semi colon
function process: ProcessingFunction (doc: Document, data: unknown): void {
...
}
function process<DType>: ProcessingFunction<Document, DType> (doc: Document, data: DType): void {
...
}
Con: The generic position could be confusing, but this could be actually valid.
Interfaces implementation
function process implements ProcessingFunction (doc, data): void {
...
}
function process<Data> implements ProcessingFunction<Data> (doc, data): void {
...
}
Pros: ES classes already have
implements
syntax in TS, so this could be convenient. Middle:implements
could have a different meaning when using with classes than with functions. With classes, an interface should be consider a contract and every property in that interface should be declared by the class. However, if a function signature is declared using an Interface or a Type, and this function signature also have additional properties, what should TS do about this? Should throw and warn about the function not having such properties?
Of course, this is something that should be investigated, but I think that the interface implementation
is the most proper syntax if we want to type function expressions, from the point of view of "What is tecnically doable" and "TS shouldn't transform JS code".
How about using the way provided by #10421?
function Test({ message }: TestProps) {
return <div>{message}</div>
}
assume Test is React.FC<TestProps>;
@maxloh probably a good workaround, but I don't think this would solve the generic issue. I think the generics issue is why this has taken so long to be even considered. It's not really as straightforward as you would think.
I don't think the generics issue has much to do with why this is taking a long time to be considered. I'm not on the TypesScript team, but I know there's a long backlog of issues and feature requests, and this request is essentially a secondary syntax for something that's already possible. Don't get me wrong, I'm the one who originally raised the issue and I'm in favor of adding this to the language, but adding better support for generics is a separate consideration. As someone already pointed out, this is a pre-existing issue with function expressions defined with const
. At this point I think it would be best if a new issue were created about the generics issue.
this request is essentially a secondary syntax for something that's already possible.
Actually, for me, is just about doing something that I'm not able to do without it. You can't declare React components properly if those components use generics, as TS would take the generic declaration as a React element.
import { FC } from "react";
interface ComponentProps<Data> {
data: Data;
}
const Component: FC<ComponentProps<Data>> = <Data>({ data }) => {
return <>{JSON.stringify(data)}</>
}
And if you use the function declaration, you are now with a function that can handle the generic and have the same shape of a React component, you are typing props, and the return, but you can't use other React features.
This might be "workarounded" if the assume
feature is implemented, with something like this:
function DeclarationComponent<Data>({ data }: ComponentProps<Data>): ReactElement | null {
return <>{JSON.stringify(data)}</>; // var data: Data
}
assume DeclarationComponent is FC<ComponentProps>
// or even better, idk if this would work though.
assume DeclarationComponent<Data> is FC<ComponentProps<Data>>
Of course, you can reassign the function to a variable, or cast it, or something, but it would better if we have a syntax to support this.
@michaeljota I would still recommend opening a separate issue about generics and the new syntax you propose to add to the existing const/variable syntax (assuming you would eventually want this feature for functions declared either way and not only for function
declarations). The points you're raising are unlikely to get any attention here in this thread for a long time. We're not even close to an official design for the original suggestion in this thread, let alone an implementation or adding additional support for generics.
I mean, it's all about the same situation. There should not a special syntax for generics, there should be a syntax to type functions, and that syntax needs to support generics. Else, what we would do if we try to type a function with a type that requires generics arguments?
@DanielRosenwasser I'm going to take my chances here, but the more I use TS and need to create a component with generics, the more I need this to be implemented. So, here is a polished version of my proposal:
implements
keyword for functions to create a function contract interface.implements
keyword is currently used to create a class contract, extending the standard ES classes with OOP features. The implements
keyword would be in the position as they are used in classes, after the generic declaration. Unlike classes, functions require parenthesis for arguments declaration. This declaration would start right after the implementation finished. The whole implements
implementation could be removed in order to produce valid JS code, as it is done in ES classes. Supporting generics would be a required feature for this to work, and having the implements
implementation after the function generic declaration allows the generic to be used in the scope of the implements
. The type or interface implemented with implements
can be anything. Call signatures in the given interfaces should be used to define the signatures of the function. If a class interface is used, the function should produce a function constructor.
interface TableProps<Data> {
data: Data;
columns: Column<Data>[];
sortBy: { key: keyof Data }
}
function Table<Data> implements FC<TableProps<Data>> ({ data, columns, sortBy }) { // props are typed correctly and a valid React node return is required.
...
}
Table.defaultProps = {} // No errors, correctly shaped as Partial<TableProps<Data>>
interface State<Data> {
data: Data;
}
import { Reducer, Action } from 'redux';
function reducer<Data> implements Reducer<State<Data>, Action> = (state, action) => { // state is typed State<Data>
//
}
I think this could be the best way this could get implemented. Anything that I can do to help with this, I'll try to do it.
Please, consider this syntax can also be applied to anonymous functions and arrow functions as well:
interface TableProps<Data> {
data: Data;
columns: Column<Data>[];
sortBy: { key: keyof Data }
}
export function<Data> implements FC<TableProps<Data>> ({ data, columns, sortBy }) { // props are typed correctly and a valid React node return is required.
...
}
interface TableProps<Data> {
data: Data;
columns: Column<Data>[];
sortBy: { key: keyof Data }
}
const Table = <Data> implements FC<TableProps<Data>> ({ data, columns, sortBy }) => { // props are typed correctly and a valid React node return is required.
...
}
Table.defaultProps = {} // No errors, correctly shaped as Partial<TableProps<Data>>
Is it possible to use a defined type with generics on function expressions already? Or would this feature add this (or at least provide an alternative)?
I've posted a more detailed question here:
@penx Thanks for asking that, and posting the link here. The answer they give inspire me to do this:
export interface Component extends VFC<ComponentProps<{}>> { <Data>(props: ComponentProps<Data>): ReturnType<FC> };
export const Component: Component = ({ data, keys }: ComponentProps<Record<any, any>>) => {
return <>{data.map(a => <h2>a</h2>)}</>
}
Declaring a type alias or an interface overload can help us to do the trick of having a component using generic types. :).
Of course, this still would be a better solution, as we wouldn't need to deal with overloads, but this workaround actually solves the issue without any tradeoff.
this workaround actually solves the issue without any tradeoff.
The trade off is complexity / readability. Although Typescript projects often already have their fair share of arcane expressions. Luckily you can hide most of them away so junior devs can just use the types without thinking about them too much (if the code base is in an okay state)
I realize this issue has been around for some time, but I just wanted to say: I still want this. When new to typescript, I had originally tried to write e.g.
type MyFn = (a: number, b: string) => number declare function h: MyFn function h(a, b) { return a }
and was very disappointed when I found out you couldn't do this.
This is probably the proposal with better readability. Is this even being considered or is stale?
I saw @michaeljota 's comment in https://github.com/microsoft/TypeScript/issues/22063#issuecomment-371514006 and was inspired to try this out in a .js file. Typing function declarations already works with jsdoc!
// perhaps in a global.d.ts file
type NumberToStringFn = (a: number) => string
/** @type {NumberToStringFn} */
function numberToStringDeclaration(num) {
return num.toString()
}
Typescript properly recognizes the type of numberToStringDeclaration
:
If https://github.com/microsoft/TypeScript/issues/42048 (jsdoc in .ts files) were implemented, this could potentially be a way to get typed function declarations without adding a new syntax.
@jekh That certainly sounds like a good feature, but the original reason I raised this issue was for consistency in syntax. The idea is to allow teams to decide their preferred coding style / linting preferences for functions without being forced to use const
expressions instead of declarations in some instances just to be able to specify the types. Having jsdoc syntax as the only option for typing a function declaration would work against the goal of consistent syntax, except for teams that might prefer to use jsdoc syntax across the board for everything (not just functions).
So while I would be in favor of supporting jsdoc syntax, I hope the TypeScript team would still leave this issue open. But in the meantime, maybe jsdoc syntax couldl help address some of the issues with typing generic functions mentioned earlier in this thread.
The JSDoc approach is definitely better than nothing but it wouldn't work with type declarations (i.e. cannot be used in .d.ts
files).
For example we'd want to support something like this:
declare function foo: F;
Note: This would be different to declare const
in the following case:
declare const foo: F; // <-- Cannot redeclare block-scoped variable 'foo'. ts(2451)
declare namespace foo { /* ... */ }
@mbrowne I fully share your desire to have first-class support in typescript for typing function declarations. In addition to stylistic and consistency benefits from it, it's currently simply not possible in typescript to get the benefits of function hoisting while also declaring the type of your function using an existing type.
The observation about jsdoc and function typing was more to point out that typescript is already capable of supporting this feature, at least internally, but limits it to .js files. It's also a possible work-around (if the referenced issue were implemented) while waiting for first-class typescript support.
I'd very much like to see this issue remain open and perhaps get a bit more discussion around what the syntax could/should be.
@jekh I'm glad you found my comment useful. I have to mention when I comment that I didn't consider function generics, that for me, it may be the only problem the TS team is having with this (I may be wrong, and probably I'm) That's because almost all proposals break when you need to declare a function with a generic. Here is a larger explanation and proposal https://github.com/microsoft/TypeScript/issues/22063#issuecomment-912753284.
not sure if this is related or not but please help if you can. I'm creating an external type that I want to import and use as a method type. Not just the input/output but completely override or overload this method because certain keys accept certain config values
Is this blocked by this issue or unrelated?
generated.ts
export type FuncType =
| ((key: 'activityPlugin.activity', config?: Record<string, unknown>) => string)
| ((key: 'activityPlugin.expenditureRate', config?: Record<string, unknown>) => string)
| ((key: 'activityPlugin.manualEntry', config?: Record<string, unknown>) => string)
.......
translations.ts
class Translations {
// how can I tell ts to type this method as FuncType???
t(key, config?) {
return this.translate(key, config);
}
}
@a7madgamal It's unrelated, and I'm not sure if you can overload the string fallback. With module augmentation you could do something like:
declare class Translations {
t(key; 'activityPlugin.activity', config?: Record<string, unknown>): string;
t(key; 'activityPlugin.expenditureRate', config?: Record<string, unknown>): string;
}
Or better yet,
type ValidKeys = 'activityPlugin.activity' | 'activityPlugin.expenditureRate' | ...;
declare class Translations {
t(key; ValidKeys, config?: Record<string, unknown>): string;
}
But this will only provide suggestions to the TS Server, I don't know if that would prevent you to input any string, because the default declaration uses a string.
Wow, so many propositions, but I haven't seen this yet, which I think is more concise and aligns with other TypeScript idioms:
export default function Home() as NextPage {
return (
<Title>Home page</Title>
);
}
NextPage
is:
type NextPage<P = {}, IP = P> = React.ComponentType<P> & {
getInitialProps?(context: NextPageContext): IP | Promise<IP>;
}
What the above allows is to preserve existing named/hoisted function declarations, with all their current functionality, by just adding an additional and optional type constraint at the end using the as
keyword, which is used for type assertions. In this case with functions, I think it's an elegant solution as this is indeed an assertion over a clear type definition (the normal function type definition).
export default function Home(): ReactElement as NextPage {
return (
<Title>Home page</Title>
);
}
The above would still be a valid declaration as Home
's signature (void => ReactElement
) is compatible with the signature of NextPage
. We're just asserting that the function Home(): ReactElement
should also satisfy the NextPage
type's constraints as well.
That's my suggestion. Please forgive any mistakes, if I've made them, as I am a novice at writing TypeScript.
Moving the content of my suggestion (#54989) to this one, seems like this would be an amazing feature that would align function declarations to function expressions in a meaningful way.
Example use case in Playground that echoes alot of what has already been mentioned. I also think this could do wonders for function overloads as well.
/**
* defined in `'some-types-from-somehwere'`
* export type LenString = (s: string) => number;
* export type LenArr = (arr: any[]) => number;
*/
import type { LenString, LenArr } from 'some-types-from-somehwere';
function<LenString | LenArr> (x) {
return x.length;
}
This has been the most confusing thing for me when learning Typescript, I would like to keep using function a()
for functions but Typescript basically forces me into using arrow functions if I want type annotations for the functions. Having this implemented so you can write the style you prefer would be great.
@henrikvilhelmberglund it is not even a matter of preference: arrow function don't have access to this
, so there is a fundamental difference betweenn the two kind of functions, and currently only one of them (arrow function) is considered as a first-class citizen, which is a bummer.
@ericmorand That is so wrong. Either function
should be a first class citizen as it has its own keyword and has way more features, or both.
If I might suggest a syntax for function type checking? I think this would work nicely:
type IMyFunc<P,R> = (params: P): R;
function MyFunc<P,R>(params: P) {
...
}: IMyFunc<P,R>;
Due to how var
and function
declaration merging works in vanilla JS, you should technically be able to do the following:
// @showEmit
declare type IMyFunc = <P, R>(params: P) => R;
var MyFunc: IMyFunc;
function MyFunc(params) {
// ^?
// ...
}
:wave: Hi, I'm the Repro bot. I can help narrow down and track compiler bugs across releases! This comment reflects the current state of this repro running against the nightly TypeScript.
Comment by @ExE-Boss
:x: Failed: -
Duplicate identifier 'MyFunc'.
Duplicate identifier 'MyFunc'.
Parameter 'params' implicitly has an 'any' type.
Version | Reproduction Outputs |
---|---|
5.0.2, 5.1.3, 5.2.2, 5.3.2, 5.4.2 |
:x: Failed: -
Emit: ```ts "use strict"; var MyFunc; function MyFunc(params) { // ... } ``` |
Due to how
var
andfunction
declaration merging works in vanilla JS, you should technically be able to do the following:// @showEmit declare type IMyFunc = <P, R>(params: P) => R; var MyFunc: IMyFunc; function MyFunc(params) { // ^? // ... }
That doesn't cover the same use-case as what I'd like to cover. In your example you are defining an interface that contains a function that is generic (and so the implementation must support any combination of P and R that the caller supplies). I don't think there are too many cases where that would really be desirable in and of itself.
What I'd like to see is a way to define a generic function type that can be applied to functions to validate that they not only satisfy the type constraints, but the generic type arguments can be refined for more specific use-cases. Much the same as we do with generic interfaces and classes.
For instance, I have a little plugin framework I've written on top of the ag-grid component so that you can have multiple things listen and respond to the GridReady event (as-is you can only pass in one event handler). I have a generic IPlugin<TData, TContext> interface and a PluginHook<TData, TContext> function type.
As I implement a hierarchy of plugins, some of which are generic and others are more specific to certain TData and/or TContexts, I would like Typescript to validate that my hook functions are properly satisfying the function type definition of a PluginHook, including any tighter type constraints placed on TData and TContext at that point in the plugin hierarchy.
Hey, Any news about it ?
I support this. Would be great to have typed hoisted functions. At least using as
keyword would already be useful
Currently in TypeScript, function declarations cannot be typed in the same way as function expressions, e.g. this function can implement the
React.FC
interface:But this function can't, at least not directly:
This becomes more of an issue if you try to add properties to the function object:
It seems that currently the only way to specify the type for the function object in the second example is to create a new variable:
This seems like kind of an ugly workaround, so it seems that the current idiom is to just prefer arrow functions for cases like this. But this leads to inconsistency on teams that generally prefer function declarations over const expressions for top-level functions. Personally I find it more readable to see the word "function" for top-level functions rather than seeing "const", which is generally already all over the place in the code. There is even an ESLint rule for teams that share my preference (although I don't think it's been ported to TSLint yet): https://eslint.org/docs/rules/func-style. In any case, I have seen others express similar views and other codebases (including some from Facebook and Apollo, for example) that still prefer the "function" keyword for top-level functions.
However, stylistically it's also a problem if top-level functions are declared some places as declarations (using
function
) and in other places as expressions (usingconst
). But for those who desire consistency, TypeScript is basically forcing the use of expressions, due to the issues described above.This is far from being a top priority of course, but I was surprised to see that TypeScript didn't provide some equivalent typing syntax for function declarations. It would be great if this could be considered for a future version (even if far in the future). Thanks for reading!