代码之家  ›  专栏  ›  技术社区  ›  Jamie Treworgy

强制数组在联合类型上是穷举的

  •  0
  • Jamie Treworgy  · 技术社区  · 7 年前

    给定使用以下技术创建的强类型元组: here :

    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 协会

    我们的目标是能够在这样的设计阶段创建一个数组,并让它失败 家具 改变理想的语法可能如下所示:

    const furniture = tuple<Furniture>('chair', 'table', 'lamp')
    
    0 回复  |  直到 7 年前
        1
  •  9
  •   jcalz    5 年前

    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 是一个 curried 函数,其唯一目的是获取手动指定的类型参数 T 然后返回一个新函数,该函数接受类型受约束的参数 T 但根据电话推断。(如果我们有适当的部分类型参数推断,可以消除currying。)就你而言, 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 link to code

        2
  •  0
  •   Przemyslaw Jan Beigert    7 年前

    我还有别的建议

    type RemoveFirstFromTuple<T extends any[]> = 
      T extends [] ? undefined :
      (((...b: T) => void) extends (a: any, ...b: infer I) => void ? I : [])
    
    const tuple = <T extends string[]>(...args: T) => args;
    
    type FurnitureUnion = 'chair' | 'table' | 'lamp';
    type FurnitureTuple = ['chair', 'table' , 'lamp'];
    
    type Check<Union, Tuple extends Array<any>> = {
      "error": never,
      "next": Check<Union, RemoveFirstFromTuple<Tuple>>,
      "exit": true,
    }[Tuple extends [] ? "exit" : Tuple[0] extends Union ? "next" : "error"];
    
    type R = Check<FurnitureUnion, FurnitureTuple>; // true
    type R1 = Check<'chair' | 'lamp' | 'table', FurnitureTuple>; // true
    type R2 = Check<'chair' | 'lamp' | 'table', ['chair', 'table' , 'lamp', 'error']>; // nerver
    

    Remove from tuple获取tuple并返回没有第一个元素的tuple(稍后需要)

    Check将在元组上迭代。每一步都可以返回 never 当元组[0]不扩展并集时,当输入元组为空时退出,当元组[0]扩展并集时退出。在下一步中,我们将递归调用检查,但首先我们将通过前面的util从元组中删除第一个元素

    Playground

    推荐文章