强制数组在联合类型上是详尽的

Jam*_*rgy 6 typescript

给定一个使用此处描述的技术创建的强类型元组:

const tuple = <T extends string[]>(...args: T) => args;
const furniture = tuple('chair', 'table', 'lamp');

// typeof furniture[number] === 'chair' | 'table' | 'lamp'
Run Code Online (Sandbox Code Playgroud)

我想在设计时断言它对另一种联合类型是详尽无遗的:

type Furniture = 'chair' | 'table' | 'lamp' | 'ottoman'
Run Code Online (Sandbox Code Playgroud)

我如何创建一个类型来确保furniture包含联合中的每个类型Furniture

目标是能够在设计时像这样创建一个数组,如果它失败应该Furniture改变;理想的语法可能如下所示:

const furniture = tuple<Furniture>('chair', 'table', 'lamp')
Run Code Online (Sandbox Code Playgroud)

jca*_*alz 8

TypeScript 并没有真正直接支持“穷举数组”。您可以引导编译器进行检查,但这对您来说可能有点麻烦。一个绊脚石是缺少部分类型参数推断(如microsoft/TypeScript#26242 中所要求)。这是我的解决方案:

type Furniture = 'chair' | 'table' | 'lamp' | 'ottoman';

type AtLeastOne<T> = [T, ...T[]];

const exhaustiveStringTuple = <T extends string>() =>
  <L extends AtLeastOne<T>>(
    ...x: L extends any ? (
      Exclude<T, L[number]> extends never ? 
      L : 
      Exclude<T, L[number]>[]
    ) : never
  ) => x;


const missingFurniture = exhaustiveStringTuple<Furniture>()('chair', 'table', 'lamp');
// error, Argument of type '"chair"' is not assignable to parameter of type '"ottoman"'

const extraFurniture = exhaustiveStringTuple<Furniture>()(
  'chair', 'table', 'lamp', 'ottoman', 'bidet');
// error, "bidet" is not assignable to a parameter of type 'Furniture'

const furniture = exhaustiveStringTuple<Furniture>()('chair', 'table', 'lamp', 'ottoman');
// okay
Run Code Online (Sandbox Code Playgroud)

如您所见,exhaustiveStringTuple是一个柯里化函数,其唯一目的是采用手动指定的类型参数T,然后返回一个新函数,该函数采用类型T受调用约束但由调用推断的参数。(如果我们有适当的部分类型参数推断,可以消除柯里化。)在您的情况下,T将指定为Furniture. 如果您只关心exhaustiveStringTuple<Furniture>(),那么您可以改用它:

const furnitureTuple =
  <L extends AtLeastOne<Furniture>>(
    ...x: L extends any ? (
      Exclude<Furniture, L[number]> extends never ? L : Exclude<Furniture, L[number]>[]
    ) : never
  ) => x;
Run Code Online (Sandbox Code Playgroud)

Playground 链接到代码