【问题标题】:Why do I have to specify parameter names in a TS function type?为什么我必须在 TS 函数类型中指定参数名称?
【发布时间】:2020-05-23 15:33:31
【问题描述】:

我正在使用 typescript 编写一些函数,这是 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];
    }
};

但一开始我想这样写useStorage的返回类型:

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

为什么 TypeScript 要我在 T 出现之前写下 newData 名称?

【问题讨论】:

    标签: javascript typescript generics typescript-generics


    【解决方案1】:

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

    在函数类型中支持参数名称作为库中函数和方法的文档很有用。函数类型(username: string, password: string) =&gt; void(arg0: string, arg1: string) =&gt; void 的类型相同,但前者可能比后者更能帮助开发人员编写代码。因此,旨在支持函数类型中的类型注释参数名称,并记录在(越来越过时的)TypeScript spec 中,并在各地的库中使用。 In the words of one of the language maintainers:

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


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

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

    xy 的类型被推断为 any(你会得到一个很好的错误 --noImplicitAny compiler option)。与以下注释版本相同:

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

    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
    

    所以当您编写(x, y) =&gt; void 时,编译器会将xy 解释为名称,而不是类型。由于类型可以省略,参数名称不能。我认为没有人喜欢这个,但这种方式已经存在很长时间了,以至于它是apparently used in libraries,所以改变它会破坏现实世界的代码。来自上面引用的相同评论:

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


    所以这就是这个问题的可悲答案。也许如果他们能及时回到过去,他们会让参数名称是可选的并且类型是必需的,以更接近于类型理论符号,但现在这是我们坚持的。

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

    Playground link to code

    【讨论】:

    • 一个非常令人筋疲力尽且有价值的答案 - 谢谢。使用 TypeScript 有时很奇怪,因为它解决了一些问题,但也带来了其他问题
    • 一个匿名类型的值可以表示为:T,这比一个字母名称或将一个复杂的类型延伸到多行以便为半-有意义的名字。
    【解决方案2】:

    以下类型别名可用于避免显式参数名称:

        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;
    

    另外,如果你主要处理 1 个参数函数,你可以使用以下类型(它有点短):

        type F1<A, R> = F<[A], R>
    
        // or
    
        type F1<A, R> = (arg: A) => R
    

    【讨论】:

      【解决方案3】:

      好吧,您正在定义一个参数,并且参数需要名称。否则,您将如何引用它?我的意思是,您在调用setItem 时使用的是newData。您不能简单地使用setItem(T),它类似于使用setItem(string)肯定不是你想要的。 我说“相似”,因为string 是 JavaScript 中的有效对象,您可以将其作为参数传递。 T,但是可以是任何东西,可能只是一个类型定义,而这些类型在编译过程中会消失。

      【讨论】:

      • 我不是在定义一个参数,而是一个返回类型,如果返回类型不是匿名的(泛型不是),它与变量名无关。老实说,我仍然相信[T, (T) =&gt; Promise&lt;void&gt;] 语法没有任何问题。您不使用 setItem(T) 而是使用 setItem(key) 将键转换为 T 类型
      • 这个答案并没有真正解决这个问题,这就是为什么函数的 type 需要参数名称。 (x: T)=&gt;void(y: T)=&gt;void 类型相同;参数名称是一个虚拟名称。我认为它们被支持的原因是为了文档((username: string, password: string) =&gt; void 类型的函数在文档中比(string, string) =&gt; void 更有用)并且它们是必需的原因是一致性并且易于解析,但这些只是我的猜测,我还没有规范的答案。
      猜你喜欢
      • 2018-09-01
      • 2021-12-31
      • 2019-02-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多