-
Notifications
You must be signed in to change notification settings - Fork 12.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: Type annotations and interfaces for function declarations #22063
Comments
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 Another proposal, should be supporting /**
* @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:
My current alternative is to do:
But unfortunately, I wouldn't be able to type them, in say, within objects. E.g.:
Or:
I could also do:
But then I would get this complaint (some might not depending on your ESLint):
|
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.
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
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
(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) // 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 In my opinion it makes the code a lot easier and faster to read because 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 |
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 ( 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:
|
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 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).
Of course, this is something that should be investigated, but I think that the |
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 |
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.
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
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 |
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:
|
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. |
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) |
This is probably the proposal with better readability. Is this even being considered or is stale? |
I saw @michaeljota 's comment in #22063 (comment) 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 If #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 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 declare function foo: F; Note: This would be different to 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 #22063 (comment). |
not sure if this is related or not but please help if you can. 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>
);
}
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 export default function Home(): ReactElement as NextPage {
return (
<Title>Home page</Title>
);
} The above would still be a valid declaration as 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 |
@henrikvilhelmberglund it is not even a matter of preference: arrow function don't have access to |
@ericmorand That is so wrong. Either |
If I might suggest a syntax for function type checking? I think this would work nicely:
|
Due to how // @showEmit
declare type IMyFunc = <P, R>(params: P) => R;
var MyFunc: IMyFunc;
function MyFunc(params) {
// ^?
// ...
} |
👋 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. ❌ Failed: -
Emit: "use strict";
var MyFunc;
function MyFunc(params) {
// ...
} Historical Information
|
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 |
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!
The text was updated successfully, but these errors were encountered: