【问题标题】:Type inference for arrays of tuple pairs元组对数组的类型推断
【发布时间】:2019-11-07 02:01:29
【问题描述】:

如何键入我的create 函数,以便它可以安全地推断它收到的每一对的单独类型?

type Component<T=any> = { id: string, _type?: T };

type Pair<T=any> = [Component<T>, T];

function create<T extends Pair[]>(...pairs: T) {
  // ...
}

type Vector = [number, number];
type Status = "active" | "idle";

let Position: Component<Vector> = { id: "position" };
let Status: Component<Status> = { id: "status" };

let entity = create(
  [Position, [0, 0]],
  [Status, false], // should fail as it expects `Status` and got `boolean`
);

Playground version

我可以通过显式传递类型参数来实现所需的行为。

let entity = create<[
  Pair<Vector>,
  Pair<Status>,
]>(
  [Position, [0, 0]],
  [Status, false], // should fail as it expects `Status` and got `boolean`
);

我的预感是,一旦编译器已经将 pairs 参数推断为 Pair[] 类型,编译器就不会尝试从它们的元素推断嵌套的 Pair 类型。

【问题讨论】:

  • 你刚刚用 Pait 定义了 Pair,所以 Pair 的类型是 [Component, any]...
  • 我知道。这就是为什么我试图弄清楚如何推断该类型。

标签: typescript


【解决方案1】:

在 TS 中获取数组项内部的这种相关性并不是很直接。如另一个答案中所述,其中一个解决方案是一对专用函数,并在每个项目上调用该函数。虽然此解决方案有效,但它并不理想,因为它会强制用户记住选择此检查。

另一种解决方案是使用自定义方式执行检查。这依赖于以下内容:

如果参数是包含类型参数和依赖于该类型参数的第二种类型的交集(例如:T &amp; SomeMappedType&lt;T&gt;),打字稿将推断类型参数T 将使用SomeMappedType&lt;T&gt; 中推断的类型和对照SomeMappedType&lt;T&gt; 检查参数。基本上,我们在T 中捕获参数的实际类型,但对依赖于T 的单独类型执行检查。

对于这个例子来说,这意味着我们将使用一个类型参数来捕获数组的实际类型作为一个元组(在你的例子中:[[Component&lt;Vector&gt;, number[]], [Component&lt;Status&gt;, boolean]]),然后使用这个类型来构建我们真正想要的类型检查(在本例中应该是[Pair&lt;Vector&gt;, Pair&lt;Status&gt;])。我们将使用一个简单的映射类型来做到这一点。

type Component<T> = { id: string, usage?: T };
type Pair<T> = [Component<T>, T];
type PairCheck<T extends Pair<any>[]> = {
  [P in keyof T]: T[P] extends [Component<infer U>, any] ? Pair<U>: never
}
function create<T extends Pair<any>[]>(...pairs: T & PairCheck<T>) {
  // ...
}

type Vector = [number, number];
type Status = "active" | "idle";

let Position: Component<Vector> = { id: "position" };
let Status: Component<Status> = { id: "status" };

let entity = create(
  [Position, [0, 0]],
  [Status, true],
);

Playground Link

注意Component 必须使用 T 才能使这些功能起作用。阅读FAQ 了解未使用的类型参数。

你得到的错误是

Argument of type '[[Component<Vector>, [number, number]], [Component<Status>, boolean]]' is not assignable to parameter of type '[[Component<Vector>, number[]], [Component<Status>, boolean]] & [Pair<Vector>, Pair<Status>]'.
  Type '[[Component<Vector>, [number, number]], [Component<Status>, boolean]]' is not assignable to type '[Pair<Vector>, Pair<Status>]'.
    Type '[Component<Status>, boolean]' is not assignable to type 'Pair<Status>'.
      Type 'boolean' is not assignable to type 'Status'

这是一个满嘴的问题,令人遗憾的是,错误范围是第一个元素而不是有问题的元素(这实际上可能是一个编译器错误),但它确实明确了 IMO 出了什么问题。

【讨论】:

    【解决方案2】:

    create 中的 rest parameter ...pairs 的所有项目都打包为一种扩展 Pair[] 的通用类型(默认情况下,T 解析为 any)。编译器将给定的参数解释为一个数组,并且数组中项目的类型是相同的,因此这里没有机会对没有类型注释的单个项目进行类型检查。

    另一种选择是您通过手动键入create 的泛型类型参数或将函数参数与函数调用分开来提供的示例:

    const t: [Pair<Vector>, Pair<Status>] = [[Position, [0, 0]], [Status, false]]; // error (OK)
    let entity = create(...t);
    

    create 中有固定数量的Pair 项要传递或所有Pair 项的复合类型已知时,此解决方案是可行的。当这是不可能的和/或以更动态的方式创建数组时,您可以使用一些辅助函数 asPair,其唯一目的是强制执行 Pair 的强类型,如下所示:

    function asPair<T>(t: Pair<T>) {
      return t;
    }
    
    let entity = create(
      asPair([Position, [0, 0]]),
      asPair([Status, false]) // error: Type 'false' is not assignable to type 'Status'. (OK)
    );
    

    Playground

    【讨论】:

      猜你喜欢
      • 2021-08-20
      • 1970-01-01
      • 2022-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-08-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多