Вывод типа из возврата регистра переключателя с Typescript - PullRequest
0 голосов
/ 02 ноября 2019

тип функций

type fn01 = (name: string) => void
type fn02 = (age: string) => void
type fn03 = (description: number) => void

У меня есть такой случай:

type Options = 'op1' | 'op2' | 'op3'

const test = (options) => {
    switch(options) {
          case 'op1':
            return fn01
          case 'op2':
            return fn02
          case 'op3':
            return fn03
          default
            return null;
        }
    }

Использование функции:

const chosenFN = test('op1');

choseFN()

При использовании выбранного FN должно отображатьсявведите из функции, которая будет попадать в корпус переключателя, но вместо этого показывает пересечение всех из них.

Чего мне не хватает? Я подумал, что поскольку используемая функция находится внутри регистра, она выведет правильный тип на основе переданного аргумента.

Спасибо, Ренан

Ответы [ 2 ]

1 голос
/ 02 ноября 2019

Для этого вам нужно использовать перегрузки функций - https://www.typescriptlang.org/docs/handbook/functions.html#overloads:

type Options = 'op1' | 'op2' | 'op3'
type fn01 = (name: string) => void
type fn02 = (age: string) => void
type fn03 = (description: number) => void

// here overloads - define output for every input
function test(a: 'op1'): fn01;
function test(a: 'op2'): fn02;
function test(a: 'op3'): fn03;

function test(options: Options) {
    switch(options) {
      case 'op1':
        return (name: string) => {}
      case 'op2':
        return (age: string) => {}
      case 'op3':
        return (description: number) => {}
      default:
        throw new Error('no such value')  
      }
}

const f = test('op1') // it is fn01
0 голосов
/ 02 ноября 2019

Компилятор обычно не определяет типы возвращаемых функций, которые зависят от того, какое конкретное значение было введено в него. Анализ типа потока управления действует для сужения типов конкретных типов переменных внутри реализации функции, поэтому оператор switch служит для того, чтобы знать, что options точно равен op2 (например) внутрирелевантный блок case, но анализ потока управления мало влияет на тип возвращаемого значения функции.

Обычно предполагаемый тип возвращаемого значения функции будет объединением всех типов, returnот функции, независимо от анализа потока управления. Это означает, что подпись test() подразумевается как function test(options: Options): fn01 | fn02 | fn03 | null.

Когда вы действительно вызовете функцию типа fn01 | fn02 | fn03 | null, у вас возникнут проблемы. С опцией --strictNullChecks компилятора вы не можете вызывать ее вообще (и вам, вероятно, следует использовать опции компилятора --strict, так как они отлавливают ошибки).

Предполагая, что у вас есть функция типа fn01 | fn02 | fn03 (и вы убедились, что это не null), вы все равно не можете ее вызвать. Поддержка вызова объединений функций была улучшена на в TypeScript 3.5, но, тем не менее, единственной безопасной вещью, которую вы можете передать объединению функций, является пересечение ее параметров. И пересечение string & string & number не имеет членов (в JavaScript нет значений, которые являются string и number), то есть это never, и, таким образом, оно полностью не вызывается.


Вот почему это не работает. Чтобы исправить это, вы должны будете использовать перегрузок функций или аннотировать функцию так: generic , где options имеет универсальный тип O extends Options.

Перегрузки просты, но на самом деле они не безопасны для типа.

Универсальный тип потенциально может быть более безопасным для типа, но не при использовании switch, который предоставляет текущее ограничение TypeScript , при котором анализ потока управления не может сузить типы переменных общего типа.

Самый безопасный способ сделать это - использовать объект отображения вместо оператора switch:

const test = <O extends Options>(options: O) => ({
    op1: fn01,
    op2: fn02,
    op3: fn03
}[options]);

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

const chosenFN = test('op1'); // (name: string) => void
chosenFN("okay") // okay
test('op2')("age is a string I guess"); // okay
test('op3')(8675309); // okay

Хорошо, надеюсь, это поможет. Удачи!

Ссылка на код

...