首页 > 解决方案 > 强制数组在联合类型上是详尽的

问题描述

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

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

// typeof furniture[number] === 'chair' | 'table' | 'lamp'

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

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

如何创建一个类型来确保furniture包含并集中的每个类型Furniture

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

const furniture = tuple<Furniture>('chair', 'table', 'lamp')

标签: typescript

解决方案


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

如您所见,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;

Playground 代码链接


推荐阅读