question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Suggestion: Type annotations and interfaces for function declarations

See original GitHub issue

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:

interface TestProps {
    message: string
}

const Test: React.FC<TestProps> = ({ message }) => {
    return <div>{message}</div>
}

But this function can’t, at least not directly:

function Test({ message }: TestProps) {
    return <div>{message}</div>
}

This becomes more of an issue if you try to add properties to the function object:

Test.defaultProps = {
    message: 'hi'
}

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:

const AliasForTest: React.FC<TestProps> = Test
AliasForTest.defaultProps = {
    message: 'hi'
}

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 (using const). 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!

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:72
  • Comments:29

github_iconTop GitHub Comments

10reactions
michaeljotacommented, Sep 16, 2021

@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.

To consider:

  • If a function implements multiple interfaces with multiple call signatures that can’t be considerer an overload, what should the function typing be? Could it create an on-the-fly union typing? Should it throw with some error giving that they can’t assign each other?

Some examples of this in pseudo-real life code:

React component example

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>>

Reducer example

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.

Update:

Please, consider this syntax can also be applied to anonymous functions and arrow functions as well:

React component example (anonymous function)

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.
  ...
}

React component example (arrow function)

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>>
8reactions
michaeljotacommented, Mar 8, 2018

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.
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

Reusable type annotation for function declaration in Typescript?
In the Typescript snippet above, I am using type alias to annotate the variable in a function expression. The type alias: type Func...
Read more >
PEP 484 – Type Hints - Python Enhancement Proposals
This PEP aims to provide a standard syntax for type annotations, opening up Python code to easier static analysis and refactoring, potential runtime...
Read more >
Interfaces in Typescript: What are they and how to use them?
Objects, including classes, are not the only things that can utilize interfaces. TypeScript interfaces may also be used to annotate functions.
Read more >
First look: adding type annotations to JavaScript - 2ality
The ECMAScript proposal “Types as comments” (by Gil Tayar, Daniel Rosenwasser, Romulo Cintra, Rob Palmer, and others) is about adding type ...
Read more >
Type Annotation in Python | Towards Data Science
Type annotations — also known as type signatures — are used to indicate the datatypes of variables and input/outputs of functions and ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Hashnode Post

No results found