首页 > 解决方案 > TypeScript - 数组的约束

问题描述

我有以下(示例)数组:

[
  'test-string', 
  foo => ({ foo }), 
  'other-string', 
  bar => ({ bar })
]

使用(示例)界面

interface MyValues { foo: string; bar: string; }

期望这种数组类型的函数必须保证所有函数的结果统一实现完整的接口。那可能吗?目前,我有:

type Fn<Values> = (part: string) => { [key in keyof Values]?: string }
type Item<Values> = string | Fn<Values>;

function extract<Values>(items: Item<Values>[]) {
  const values = {} as Values;
  // (...)
  return values;
}

但是,这种类型只检查所有函数是否返回与键匹配的对象,Values而不是所有键最终都存在。

我不太确定这种检查是否可以使用 TypeScript,我发现这个答案也适用于类型的“计算”,但我不确定这是否适用于这个用例。

标签: typescriptgenericstypestypescript-typingsstatic-typing

解决方案


我非常有信心您可以编写任何类型,其成员是您想要的形式的数组。如果让 TypeScript 将数组的元素类型推断为其成员类型的并集,则可以验证元素类型是否覆盖了整个接口:

interface MyValues { foo: string; bar: string; }

// From jcalz, https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

type Fn<Values> = (part: string) => { [key in keyof Values]?: string }
type Item<Values> = string | Fn<Values>;
type CheckComplete<Values, T extends Item<Values>> =
  // note: ReturnType is distributive.
  UnionToIntersection<ReturnType<Exclude<T, string>>> extends
  { [key in keyof Values]: string } ? unknown : never;

function extractFactory<Values>() {
  return function<T extends Item<Values>>(items: T[] & CheckComplete<Values, T>) {
    const values = {} as Values;
    // (...)
    return values;  
  };
}

const extract = extractFactory<MyValues>();

const a = [
  'test-string', 
  foo => ({ foo }), 
  'other-string', 
  bar => ({ bar })
];
extract(a);  // OK

const b = [
  'test-string', 
  foo => ({ foo }), 
  'other-string'
];
extract(b);  // Error

但是,使用将元素类型设置为联合的注释很容易绕过检查,并非所有成员都实际出现在数组中:

extract<string
  | ((arg: string) => {foo: string})
  | ((arg: string) => {bar: string})>(b);  // Wrong but no error

推荐阅读