元组对数组的类型推断

时间:2019-09-26 11:00:44

标签: typescript

如何键入我的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

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

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

我的直觉是,一旦编译器已经推断出Pair的类型为pairs,就不会尝试从其元素中推断嵌套的Pair[]类型。

2 个答案:

答案 0 :(得分:1)

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

一种替代方法是您通过手动输入any的泛型类型参数或将函数参数与函数调用分开来提供的示例:

create

当要在const t: [Pair<Vector>, Pair<Status>] = [[Position, [0, 0]], [Status, false]]; // error (OK) let entity = create(...t); 中传递固定数量的Pair项时,或者所有create项的复合类型已知时,此解决方案是可行的。如果无法实现和/或以更动态的方式创建数组,则可以使用一些辅助函数Pair,其单个目的是强制执行asPair的强类型,如下所示:

Pair

Playground

答案 1 :(得分:1)

在TS中,在数组项内部获取这种相关性不是很简单。如另一个答案所述,一种解决方案是将一对专用功能配对,并在每个项目上调用该功能。尽管此解决方案有效,但它并不理想,因为它迫使用户记住选择加入此检查。

另一种解决方案是使用自定义方式执行检查。这取决于以下条件:

如果参数是包含类型参数和依赖该类型参数的第二种类型(例如:T & SomeMappedType<T>)的交集,则类型脚本将推断类型参数T并将在其中使用推断的类型SomeMappedType<T>并对照SomeMappedType<T>检查参数。基本上,我们捕获了T中参数的实际类型,但是对依赖于T的单独类型执行检查。

对于本示例来说,这意味着我们将使用类型参数将数组的实际类型捕获为元组(在您的示例中为[[Component<Vector>, number[]], [Component<Status>, boolean]]),然后使用此类型来构建我们要实际上要检查(在此示例中,应该为[Pair<Vector>, Pair<Status>])。我们将使用简单的映射类型来做到这一点。

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的错误。