为什么必须在 TS 函数类型中指定参数名称?

Bar*_*zek 3 javascript generics typescript typescript-generics

我正在使用打字稿编写一些函数,这是 TS 接受的函数:

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];
    }
};
Run Code Online (Sandbox Code Playgroud)

useStorage但最初我想这样写返回类型:

[T, (T) => Promise<void>]
Run Code Online (Sandbox Code Playgroud)

newData为什么 TypeScript 要我在发生之前写下名字T

jca*_*alz 9

我认为最接近规范答案的是 GitHub 问题 microsoft/TypeScript#13152microsoft/TypeScript#3081。情况的要点是这样的:

在函数类型中支持参数名称作为库中函数和方法的文档很有用。函数类型(username: string, password: string) => void与 类型相同(arg0: string, arg1: string) => void,但前者可能比后者更有助于开发人员编写代码。因此,旨在支持函数类型中的类型注释参数名称,记录在(越来越过时的)TypeScript 规范中,并在各地的库中使用。 用一位语言维护者的话来说

我......相信它有助于文档。在以我拥有的能力使用 Haskell 之后,我会说省略参数名称......当他们学习一个新库时,对任何人都没有帮助。虽然有一些关于更简单的语法的说法,但类型是人们唯一的文档形式,通常很难理解意图......


此外,在 TypeScript 支持命名值的类型注释的情况下,可以省略类型,编译器会推断它们。未能推断出任何有用的结果会导致 的推断any。例如,在以下函数中:

function f(x, y): void { }
type F = typeof f;
// type F = (x: any, y: any) => void
Run Code Online (Sandbox Code Playgroud)

xand的类型y被推断为any(并且--noImplicitAny 编译器选项会出现一个很好的错误)。它与以下注释版本相同:

function g(x: any, y: any): void { }
type G = typeof g;
// type G = (x: any, y: any) => void
Run Code Online (Sandbox Code Playgroud)

将同样的规则的类型签名fg自己导致以下行为:

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
Run Code Online (Sandbox Code Playgroud)

因此,当您编写时(x, y) => void,编译器将xand解释y名称而不是类型。由于类型可以省略,参数名称不能。我认为没有人喜欢这种方式,但这种方式已经存在了很长时间,显然已用于库中,因此更改此方式会破坏现实世界的代码。来自上面引用的同一评论:

我认为现在做出这种改变为时已晚。由于当前的行为,将单个标识符解释为类型将是一个重大变化。


所以这就是这个问题的悲伤答案。也许如果他们能回到过去,他们会让参数名称是可选的,类型是必需的,以便与类型理论符号更紧密地对齐,但现在这就是我们所坚持的。

希望有所帮助;祝你好运!

Playground 链接到代码