Умелое использование TypeScript (5) -- вывод

внешний интерфейс TypeScript

представлять

inferвпервые появился здесьPRв, значит вextendsПеременная типа, которая будет выводиться в условном выражении.

Простой пример выглядит следующим образом:

type ParamType<T> = T extends (param: infer P) => any ? P : T;

в этом условном выраженииT extends (param: infer P) => any ? P : Tсередина,infer PПредставляет параметр функции, который необходимо вывести.

Все предложение выражается как: еслиTможет быть назначен на(param: infer P) => any, то результат(param: infer P) => anyпараметры типаP, иначе он возвращается какT.

interface User {
  name: string;
  age: number;
}

type Func = (user: User) => void

type Param = ParamType<Func>;   // Param = User
type AA = ParamType<string>;    // string

встроенный тип

В версии 2.8 TypeScript имеет встроенныйinferСоответствующие типы отображения:

  • Тип возвращаемого значения, используемый для извлечения типа функции:

    type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any;
    

    По сравнению с примером, приведенным в начале статьи,ReturnType<T>просто поставьinfer PПереместитесь из позиции параметра в позицию возвращаемого значения, так что на этот разPТо есть он представляет тип возвращаемого значения, который необходимо вывести.

    type Func = () => User;
    type Test = ReturnType<Func>;   // Test = User
    
  • Используется для извлечения типа параметра (экземпляра) в конструкторе:

    Конструктор может использоватьnewдля создания экземпляра, поэтому его тип обычно представляется следующим образом:

    type Constructor = new (...args: any[]) => any;
    

    когдаinferИспользуется в типах конструктора, может использоваться в позиции параметраnew (...args: infer P) => any;и местоположение возвращаемого значенияnew (...args: any[]) => infer P;.

    Таким образом, встроены следующие два типа сопоставления:

    // 获取参数类型
    type ConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any ? P : never;
    
    // 获取实例类型
    type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any;
    
    class TestClass {
    
      constructor(
        public name: string,
        public string: number
      ) {}
    }
    
    type Params = ConstructorParameters<typeof TestClass>;  // [string, numbder]
    
    type Instance = InstanceType<typeof TestClass>;         // TestClass
    

некоторые варианты использования

До сих пор, я полагаю, у вас естьinferТеперь, когда у нас есть базовое понимание, давайте взглянем на некоторые «дерзкие операции», которые его используют:

  • tupleПеременаunion,Такие как:[string, number] -> string | number

    Прежде чем ответить, нужно понять, что тип кортежа может быть присвоен типу массива при определенных условиях:

    type TTuple = [string, number];
    type TArray = Array<string | number>;
    
    type Res = TTuple extends TArray ? true : false;    // true
    type ResO = TArray extends TTuple ? true : false;   // false
    

    Поэтому в сотрудничестве сinfer, это легко сделать:

    type ElementOf<T> = T extends Array<infer E> ? E : never
    
    type TTuple = [string, number];
    
    type ToUnion = ElementOf<TTuple>; // string | number
    

    существуетstackoverflowСм. другое решение выше, относительно простое (крупный рогатый скот) и одиночное (принудительное):

    type TTuple = [string, number];
    type Res = TTuple[number];  // string | number
    
  • unionПеременаintersection,Такие как:string | number -> string & number

    Это может быть немного более хлопотно, нужноinferСотрудничать"Distributive conditional types"использовать.

    существуетСсылки по теме, мы можем узнать, что «дистрибутивные условные типы» — это условные типы, состоящие из «параметра голого типа». А "параметр голого типа" означает, что он не былWrappedтип (например:Array<T>,[T],Promise<T>и т. д. не являются «параметром голого типа»). «Распределительные условные типы» в основном используются для разделенияextendsТип объединения левой части, например: в условном типеT extends U ? X : Yв, когдаTдаA | B, он будет разделен наA extends U ? X : Y | B extends U ? X : Y;

    С этой предпосылкой повторно используйте его в положении инвертора,Несколько типов-кандидатов одной и той же переменной типа будут выведены как перекрестные типы.характеристики, то есть

    type Bar<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
    type T20 = Bar<{ a: (x: string) => void, b: (x: string) => void }>;  // string
    type T21 = Bar<{ a: (x: string) => void, b: (x: number) => void }>;  // string & number
    

    Следовательно, совмещая вышеперечисленные пункты, мы можем получитьstackoverflowОтвет выше:

    type UnionToIntersection<U> =
      (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
    
    type Result = UnionToIntersection<string | number>; // string & number
    

    при входящемstring | numberВремя:

    • первый шаг:(U extends any ? (k: U) => void : never)расколет союз на(string extends any ? (k: string) => void : never) | (number extends any ? (k: number)=> void : never), то есть получить(k: string) => void | (k: number) => void;

    • Шаг 2:(k: string) => void | (k: number) => void extends ((k: infer I)) => void ? I : never, исходя из вышеизложенного, можно сделать вывод, чтоIдляstring & number.

Конечно, вы можете сыграть и другие трюки, напримерunionПеременаtuple.

Вопрос интервью TypeScript от LeetCode

некоторое время назад, вGitHubЯ нашел интересный вопрос интервью от LeetCode TypeScript, Общий смысл вопроса:

Предположим, что есть такой тип (класс, указанный в исходном вопросе, здесь упрощен до интерфейса):

interface Module {
  count: number;
  message: string;
  asyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>;
  syncMethod<T, U>(action: Action<T>): Action<U>;
}

проходя черезConnectПосле функции тип возвращаемого значения

type Result {
  asyncMethod<T, U>(input: T): Action<U>;
  syncMethod<T, U>(action: T): Action<U>;
}

вAction<T>определяется как:

interface Action<T> {
  payload?: T
  type: string
}

Вот два основных момента

  • выбрать функцию
  • Тип состояния + упомянутый в этой статьеinfer

метод выбора функций, уже вhandbookЭто было дано в , просто оцените, что значение может быть присвоено функции:

type FuncName<T>  = {
  [P in keyof T]: T[P] extends Function ? P : never;
}[keyof T];

type Connect = (module: Module) => { [T in FuncName<Module>]: Module[T] }
/*
 * type Connect = (module: Module) => {
 *   asyncMethod: <T, U>(input: Promise<T>) => Promise<Action<U>>;
 *   syncMethod: <T, U>(action: Action<T>) => Action<U>;
 * }
*/

Следующий шаг относительно прост, в основном используется тип условия +infer, если функция может быть назначенаasyncMethod<T, U>(input: Promise<T>): Promise<Action<U>>, то значение равноasyncMethod<T, U>(input: T): Action<U>. Конкретного ответа не дано, и заинтересованные друзья могут попробовать.

Более

Ссылаться на

Для получения дополнительных статей, пожалуйста, обратите внимание на наш публичный аккаунт:

微信服务号