簡體   English   中英

如何在不丟失推斷的泛型類型的情況下驗證泛型可變參數映射元組參數?

[英]How to validate generic variadic mapped tuple arguments without losing the inferred generic types?

我有一個高階函數createHandler ,它以可變映射元組的形式接受剩余參數,將它們映射到通用對象類型(我們現在稱它為ObjA<> )。 然后可以將返回的函數傳遞給另一個高階函數useHandler ,該函數將調用它(顯然在此過程中做更多事情)。

我斷言從createHandler返回的函數類型是一種特定類型(只是將它與“品牌”相交,即BrandedValidHandler ),因此只有useHandler可以接受並調用它。

在接受它作為有效輸入之前,我想對createHandler的參數數組執行一些驗證。 具體來說,我想檢查ObjA<>字段中的重復字符串,如果有重復則拒絕輸入。 我遇到的問題是在createHandler的參數數組上執行重復項檢查,而不會丟失泛型類型推斷(特別是一些很棒的上下文類型)。

有沒有辦法在不丟失此推論的情況下對輸入執行此檢查?

我想出的解決方案是改為驗證createHandler返回類型中的輸入,並返回某種類型的錯誤而不是BrandedValidHandler 期望是createHandleruseHandler將僅相互結合使用,因此這“有效”,但最好是輸入本身無效,而不是返回類型。 這是一個最小的、相當人為的、帶注釋的示例(我實際上並沒有檢查User對象中的重復名稱,但重點是這樣的):


type User<Name extends string, Email extends string> = {
  name: Name;
  email: Email;
  // The handler can use the exact name and email provided above
  handler: (name: Name, email: Email) => void;
};

// Homomorphic mapped tuple allows us infer Names and Emails
type MapUsers<Names extends string[], Emails extends string[]> =
  (
    {
      [K in keyof Names]: User<Names[K], K extends keyof Emails ? Emails[K] : never>
    }
    &
    {
      [K in keyof Emails]: User<K extends keyof Names ? Names[K] : never, Emails[K]>
    }
  );


// a type describing a valid creation of a `createUsers` handler
type ValidUsersFunc = (eventType: 'a' | 'b') => Promise<string> & { __brand: 'ValidUsersFunc' };

// check for duplicate names amongst any of the User objects
type IsUniqueUsersNames<Users extends readonly User<any, any>[]> =
  Users extends readonly [infer U extends User<any, any>, ...infer Rest extends User<any, any>[]]
  ? U['name'] extends Rest[number]['name']
  ? Error & { reason: `Duplicate name: '${U['name']}'` }
  : IsUniqueUsersNames<Rest>
  : ValidUsersFunc;

// Higher order function to return function that can be called by `useUsers`
const createUsers = <Names extends string[], Emails extends string[]>(...users: MapUsers<Names, Emails>) =>
  (async (eventType: 'a' | 'b') => {
    console.log(eventType);
    users.forEach(console.log)
    return "";
  }) as unknown as IsUniqueUsersNames<(typeof users)>; // pretty cool that we can use (typeof users) here though, I might add!

const users = createUsers(
  {
    name: 'conor',
    email: 'conor@example.com',
    handler: (name, email) => {
      name; // contextually typed as 'conor'
      email; // contextually typed as 'conor@example.com'
    }
  },
  {
    name: 'joe',
    email: 'joe@example.com',
    handler: (name, email) => {
      name;
      email;
    }
  },
  {
    name: 'conor', // uh oh, can't have 'conor' twice
    email: 'conor@google.com',
    handler: (name, email) => {
      name;
      email;
    }
  }
);
const useUsers = async (users: ValidUsersFunc) => {
  return await users('a');
};

// ERROR, which is good!
// Argument of type 'Error & { reason: "Duplicate name: 'conor'"; }' is not assignable to parameter of type 'ValidUsersFunc'.
useUsers(users);

相反,我想做的是使用我在MapUsers中創建的映射類型,並在那里執行重復檢查。 然而,這樣做會失去通用推理:

type MapUsers<Names extends string[], Emails extends string[]> =
  (
    {
      [K in keyof Names]: User<Names[K], K extends keyof Emails ? Emails[K] : never>
    }
    &
    {
      [K in keyof Emails]: User<K extends keyof Names ? Names[K] : never, Emails[K]>
    }
  ) extends (infer users extends User<any, any>[] // Uh oh, `any`!
  // This creates an issue, using `any` loses all type inference gained above in the mapped tuple intersection (I think).
  ? IsUniqueUsersNames<users> extends true // note: would be a different implementation of `IsUniqueUsersNames`, just returns a boolean instead
  ? users
  : Error
  : never;

嘗試 2: extends infer users extends any[] :丟失上下文類型

嘗試 3: extends infer users :可以毫無怨言地創建一個新的映射類型,但是隨后...userscreateUsers中不被識別為數組。

嘗試 4 抓住救命稻草:失去上下文打字

extends infer users extends User<any, any>[]
  ? {
    [K in keyof users]: users[K] extends User<infer N, infer E> ? User<N, E> : never
  }

我正在尋找的是一種在infer users行中“存儲”或“記住”泛型的方法,但我不確定這是否可行。 顯然, User類型不能獨立存在。

為了完全透明, 建立在我從我問的另一個問題中學到的基礎上,並使用從這里檢查數組中的重復項的方法。

一般來說,我會用

import {F} from 'ts-toolbelt'

function validate<T extends any[]>(
  ...args: T extends Validate<T> ? T : F.NoInfer<Validate<T>>
): void {}

type Validate<T extends any[]> = Unique<T>

其中 Validate 通過將錯誤值替換為[..., Error, ...][..., never, ...]或其他任何內容來使類型或多或少正確

我用過的其他一些問題的游樂場

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM