最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Why do I have to specify parameter names in a TS function type? - Stack Overflow

programmeradmin2浏览0评论

I am using typescript to write some function and this is the one that TS accepts:

export const useSomething = <T>() => {
    const useStorage = <T>(key: string, initialData: T) : [T, (newData: T) => Promise<void>] => {
        const [data, setState] = useState<T>(initialData);
        const setData = async(newData: T) : Promise<void> => {
            await storage.setItem<T>(key, newData);
        };
        return [data, setData];
    }
};

But inithially I wanted to write the return type of useStorage in this way:

[T, (T) => Promise<void>]

Why TypeScript wants me to write down the newData name before the T occurs?

I am using typescript to write some function and this is the one that TS accepts:

export const useSomething = <T>() => {
    const useStorage = <T>(key: string, initialData: T) : [T, (newData: T) => Promise<void>] => {
        const [data, setState] = useState<T>(initialData);
        const setData = async(newData: T) : Promise<void> => {
            await storage.setItem<T>(key, newData);
        };
        return [data, setData];
    }
};

But inithially I wanted to write the return type of useStorage in this way:

[T, (T) => Promise<void>]

Why TypeScript wants me to write down the newData name before the T occurs?

Share Improve this question edited Oct 29, 2020 at 19:25 jcalz 328k29 gold badges435 silver badges440 bronze badges asked Feb 7, 2020 at 13:24 Bartłomiej SobieszekBartłomiej Sobieszek 2,8002 gold badges28 silver badges44 bronze badges 0
Add a comment  | 

3 Answers 3

Reset to default 21

I think the closest thing you can hope for to a canonical answer is in the GitHub issues microsoft/TypeScript#13152 and microsoft/TypeScript#3081. The gist of the situation is this:

Supporting parameter names in function types is useful as documentation for functions and methods in libraries. A function type (username: string, password: string) => void is the same type as (arg0: string, arg1: string) => void, but the former probably helps developers write code more than the latter does. So support for type-annotated parameter names in function types is intended, documented in the (increasingly outdated) TypeScript spec, and used in libraries all over the place. In the words of one of the language maintainers:

I ... believe that it helps with documentation. Having used Haskell in the capacity that I have, I will say that omitting parameter names ... doesn't help anybody when they're learning a new library. While there's something to be said about an easier syntax, types being the only form of documentation for people often makes it difficult to understand intent ...


Furthermore, where type annotations on named values are supported in TypeScript, the types can be omitted and they will be inferred by the compiler. Failure to infer anything useful results in inferences of any. For example, in the following function:

function f(x, y): void { }
type F = typeof f;
// type F = (x: any, y: any) => void

the type of x and y is inferred as any (and you get a nice error with the --noImplicitAny compiler option). It's the same as the following annotated version:

function g(x: any, y: any): void { }
type G = typeof g;
// type G = (x: any, y: any) => void

Applying the same rule to the type signatures of f and g themselves leads to the following behavior:

type Fprime = (x, y) => void; // --noImplicitAny yells at you here
// type Fprime = (x: any, y: any) => void

type Gprime = (x: any, y: any) => void;
// type Gprime = (x: any, y: any) => void

So when you write (x, y) => void, the compiler interprets x and y as names and not as types. Since the type can be omitted, parameter names cannot. I don't think anyone likes this, but it's been this way for so long that it's apparently used in libraries, so changing this would break real-world code. From the same comment quoted above:

I think it is too late to make this sort of change. Because of the current behavior, interpreting a single identifier as a type would be a breaking change.


So that's the sad answer to this question. Maybe if they could go back in time they would make it so that the parameter name is optional and the type is required, to align more closely with type theory notation, but for now this is what we're stuck with.

Hope that helps; good luck!

Playground link to code

Following type alias can be used to avoid explicit parameter names:

    type F<A extends unknown[], R> = (...args: A) => R
    
    const example1: F<[number, boolean], number> = (x, y) => y ? x : x + 1;
    const example2: F<[number], string> = String;

Also if you mostly deal with 1 argument functions, you may use following type (it is a bit shorter) :

    type F1<A, R> = F<[A], R>

    // or

    type F1<A, R> = (arg: A) => R

Well, you are defining a parameter, and parameters need names. Otherwise, how would you refer to it? I mean, you are using newData when calling setItem. You can't simply use setItem(T), it would be the similar to using setItem(string), which most surely is not what you want. And I say 'similar', because string is a valid object in JavaScript, and you can pass it as parameter. T, however can be any thing, probably just a type definition, and those types disappear during compilation.

发布评论

评论列表(0)

  1. 暂无评论