Зачем нужно ключевое слово infer в Typescript?

Почему ребята из Typescript создали ключевое слово infer? Согласно документам, это пример того, как вы будете его использовать:

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

Я не понимаю, зачем это нужно. Почему нельзя просто:

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

Почему это не работает? Зачем нужно ключевое слово infer?


person CodyBugstein    schedule 04.02.2020    source источник


Ответы (3)


При использовании infer компилятор гарантирует, что вы объявили все переменные типа явно. Например. переменные типа могут быть определены:

  • после infer в предложении extends условного типа → R в T extends (...args: any[]) => infer R
  • в левой части объявления типа → T в ReturnType<T> = ...
  • как часть сопоставляемого типаK в type Mapped<T> = { [K in keyof T]: ... }

Использование необъявленных параметров типа теперь может привести к ошибке компиляции. Без infer компилятор не знал бы, хотите ли вы ввести дополнительную переменную типа (скажем, R), которая должна быть выведена, или если R является просто случайной опечаткой/опечаткой.

type R = { a: number }

type MyType<T> = T extends infer R ? R : never; // infer new variable R from T
type MyType2<T> = T extends R ? R : never; // compare T with above type R
type MyType3<T> = T extends R2 ? R2 : never; // error, R2 undeclared

type T1 = MyType<{b: string}> // T1 is { b: string; }
type T2 = MyType2<{b: string}> // T2 is never

Playground

person ford04    schedule 05.02.2020
comment
Я бы ожидал, что MyType2 сработает. T extends R должен означать любой тип, расширяющий R, который мы определили выше. Что вы подразумеваете под сравнить T с указанным выше типом R? - person CodyBugstein; 04.07.2021
comment
type MyType2<T> = ... — псевдоним типа declaration, в котором переменная/параметр универсального типа T еще не может быть разрешена. Позже мы создаем экземпляр T с {b: string}, используя ссылку на тип MyType2<{b: string}> внутри объявления псевдонима типа type T2 = .... Теперь фактический тип может быть разрешен. Поскольку {b: string} (экземпляр T) нельзя назначать (это более конкретное выражение для сравнения с) на R - что равно {a: number} -, T2 разрешается в never. Надеюсь, это проясняет ситуацию. - person ford04; 04.07.2021
comment
переменная/параметр универсального типа T еще не может быть разрешена... Но это будет иметь место для ВСЕХ универсальных типов, не так ли? - person CodyBugstein; 05.07.2021

Рассмотрим следующий код:

interface Example {
    foo: string
}

type GenericExample<T> = T extends Examlep ? 'foo' : 'bar';

Этот код должен привести к ошибке компиляции, потому что Examlep написано неправильно; нет типа с именем Examlep, и, очевидно, программист хотел написать здесь Example.

Теперь представьте, что ключевое слово infer не нужно в предложении extends условного типа. Тогда приведенный выше код не выдаст ошибку компиляции; он увидит, что нет типа с именем Examlep, сделает вывод, что это за тип, а затем (поскольку Examlep не имеет ограничений) заметит, что T действительно расширяет Examlep для выведенного типа.

В этом случае GenericExample<T> будет всегда равно 'foo', независимо от того, что такое T, и не будет ошибки компиляции, сообщающей программисту об ошибке. Это было бы неправильно для компилятора, почти все время.

person kaya3    schedule 04.02.2020
comment
Итак, в основном infer есть ли вместо какой-то глобальной настройки tsconfig указание компилятору теперь разрешать неопределенные типы? - person CodyBugstein; 06.02.2020
comment
infer означает, что вы знаете, что объявляете новый тип (в области действия условного типа) - так же, как вы должны написать var, let или const, чтобы сообщить компилятору, что вы знаете, что объявляете новую переменную. - person kaya3; 07.02.2020
comment
Но R не новый тип. Это просто заполнитель - person CodyBugstein; 07.02.2020
comment
Это переменная типа, которая является своего рода типом. Он не существовал вне области действия условного типа, поэтому он новый. - person kaya3; 07.02.2020

Ключевое слово infer позволяет вам вывести тип из другого типа внутри условного типа. Вот пример:

type UnpackArrayType<T> = T extends (infer R)[] ? R: T;
type t1 = UnpackArrayType<number[]>; // t1 is number

UnpackArrayType — это условный тип. Он читается как «Если T является подтипом (infer R)[] , вернуть R. В противном случае вернуть T».

Для псевдонима типа t1 условие в UnpackArrayType истинно, поскольку number[] совпадает с (infer R)[]. В результате процесса вывода переменная типа R выводится как числовой тип и возвращается из истинной ветви. Infer должен сообщить компилятору, что переменная нового типа R объявлена ​​внутри

the scope of UnpackArrayType.
type t2 = UnpackArrayType<string>; //t2 is string

Для t2 условие в UnpackArrayType ложно, так как строковый тип не совпадает с (infer R)[] , поэтому он возвращается как строка. Для получения дополнительной информации см. эту статью. https://javascript.plainenglish.io/typescript-infer-keyword-explained-76f4a7208cb0?sk=082cf733b7fc66228c1373ba63d83187

person peanut    schedule 24.07.2021