【问题标题】:In Typescript, define a type for an array where the first element is more specific than the rest在 Typescript 中,为数组定义一个类型,其中第一个元素比其他元素更具体
【发布时间】:2017-11-08 04:24:57
【问题描述】:

我想为一个数组定义一个类型,它的第一个元素是特定类型(例如函数),其余元素是空类型。例如:

type FAs = [Function, {}, {}, {}, ...]; // pseudo code

这样的事情可能吗?

目的是提供这样的单参数函数:

const myCaller = ([fun, ...args]: FAs) => fun.apply(args);

另一种方法是对myCaller 使用两个参数,如下所示:

const myCaller = (fun: Function, args: any[]) => fun.apply(args);

但出于美学原因,我更喜欢使用单个参数。我还想知道类型系统是否支持可以说是任意长度的元组。也许出于我不理解的计算机科学原因,这种事情是不可取的。

【问题讨论】:

标签: typescript types


【解决方案1】:

我很确定这是 Typescript 2.3 中你能做到的最好的。例如,你可以在 lodash 中看到这样的类型。

interface IMyCaller {
  <R>([fn]: [() => R]): R;
  <R,A>([fn, a]: [(a: A) => R, A]): R;
  <R,A,B>([fn, a, b]: [(a: A, b: B) => R, A, B]): R;
  <R,A,B,C>([fn, a, b, c]: [(a: A, b: B, c: C) => R, A, B, C]): R;
  // keep adding these until you get tired
}

const myCaller: IMyCaller = ([fun, ...args]) => fun.apply(args);

【讨论】:

  • 这也是我的想法,但 Typescript 对未明确定义的元素使用联合类型非常有帮助。请参阅接受的答案以获得出色的描述,以及一个重要的警告。
  • 你是对的,如果你不需要严格的输入,它会很有帮助。我想我被带走了,并没有非常关注你的问题:)
【解决方案2】:

如果你定义

type FAs = [Function, {}];

那么FAs 类型的值将需要Function 类型的第一个元素、{} 类型的第二个元素以及Function | {} 类型的后续元素。这就是 TypeScript 文字数组类型的工作方式。来自TS docs

当访问已知索引集之外的元素时,将使用联合类型:

这应该可以满足您的所有需求除了,因为您可以将Function-typed 值作为数组的第三个元素等传递。但实际上无论如何都是这样,因为Function{} 兼容。

没有办法解决这个问题。在 TS 中没有办法定义一个数组类型,其中前 n 个元素属于某些特定类型,并且存在任意数量的其他特定类型的剩余元素。

我还想知道类型系统是否支持可以说是任意长度的元组。

实际上,类型系统支持任意长度的元组。如果你说

type Tuple = [number, number];

此类型与任何长度为 2或更大且包含数字的数组兼容。如果你说

type Tuple = [string, number];

此类型与长度为 2或更长的任何数组兼容,该数组的第一个元素为字符串,第二个元素为数字,第三个元素为字符串或数字,依此类推。我不会将这种行为的原因称为“基于计算机科学”;更重要的是 TS 检查什么是可行的。

另一种方法

interface Arglist {
  [index: number]: object;
  0: Function;
}

const a1: Arglist = [func];
const a2: Arglist = [22];                  // fails
const a3: Arglist = [func, "foo"];         // fails
const a4: Arglist = [func, obj];
const a5: Arglist = [func, obj, obj];

【讨论】:

  • 我不知道 Typescript 会为已知索引集之外的元素使用联合类型,尽管我已经多次阅读该文档页面。感谢您的清晰解释和文档链接。
  • 不幸的是,这对我使用 Typescript 3.0.3 不起作用。当我声明一个使用这种类型的变量时,它抱怨长度不匹配。至于替代方法,我想使用地图、过滤器等,因此不得不修改接口以包含这些定义。
  • @FTWinstone 上面的 type 声明具有任意长度,需要在结果数组中包含 2 个或更多元素。此外,第二个之后的任何元素的类型将不限于string|number 而不仅仅是number。要使类型定义更接近上面的接口,您可以使用type myType = [string, ...number];,这意味着第一个元素必须是字符串,而任何其他元素,如果有,必须是数字。
  • 看起来这个答案现在已经过时了?这个问题有newer 的答案,including TypeScript 4.0
【解决方案3】:

在当前版本的 Typescript 中,这可以使用数组扩展:

type FAs = [Function, ...Array&lt;{}&gt;]

它支持从1到n的任意长度(第一个元素是必需的)。

【讨论】:

    【解决方案4】:

    我还想知道类型系统是否支持可以说是任意长度的元组。

    自 TS 4.0 起,您可以使用variadic tuple types。 例如,以类型安全的方式断言 [func, ...&lt;proper func args&gt;]

    type FAs<A extends unknown[], R> = [(...args: A) => R, ...A]
    
    const myCaller = <A extends unknown[], R>([fn, ...args]: FAs<A, R>) =>
        fn.apply(null, args)
    
    例子:
    const fn1 = (a1: string, a2: number) => true
    
    const r1 = myCaller([fn1, "foo", 42]) // OK, r1 has type `boolean`
    const r2 = myCaller([fn1, "foo", "bar"]) // error, `bar` has wrong type
    const r3 = myCaller([fn1, "foo"]) // error, not enough arguments
    

    Playground

    【讨论】:

      【解决方案5】:
      type First<T> = T extends [infer U, ...any[]] ? U : any;
      
      type F = First<[number, boolean, string]> // number
      

      【讨论】:

      • 能有反转版type Rest&lt;T&gt;吗? type F = Rest&lt;[number, boolean, string]&gt; // [boolean, string]
      【解决方案6】:

      假设您想要一个至少有两个元素的Array,第一个是A 类型,然后是B 类型。 更通用的方法是定义交叉类型如下:

      type A = string
      type B = number
      
      // ensures that there are at least two elements
      interface Foo {
        0: A;
        1: B;
      }
      
      // ensures that the first element is of type A and any after that of type B
      type Bar = [A, ...B[]]
      
      type MyArrayType = Foo & Bar
      
      let a: MyArrayType
      
      a = ['hello world', 1] // works
      a = ['hello world', 1, 2] // works
      a = ['hello world'] // fails
      a = ['hello', 'world'] // fails
      

      请注意,即使 A 不是 B 的子集,这也有效。接受的答案需要这样做。

      我意识到原始问题不需要此约束。但是,由于我遇到了具有这些限制的线程,它可能会对其他人有所帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-08-22
        • 2020-12-22
        • 1970-01-01
        • 2019-07-05
        • 1970-01-01
        • 2011-07-10
        • 1970-01-01
        相关资源
        最近更新 更多