Как использовать дискриминант в обобщенных функциях в TypeScript? - PullRequest
0 голосов
/ 23 декабря 2018

При использовании различающихся объединений с обобщениями мне нужно написать некоторый избыточный код, который также может нанести вред безопасности типов.

interface Square {
    kind: "square";
    size: number;
}
interface Rectangle {
    kind: "rectangle";
    width: number;
    height: number;
}
interface Circle {
    kind: "circle";
    radius: number;
}
type Shape = Square | Rectangle | Circle;

function<T extends Shape> fetch(type: Shape['kind'], id: string): T {
  // fetch data from database via id
}

Проблема в том, что в функции fetch есть спецификация избыточного типа,Я могу использовать Shape['kind'], чтобы ограничить тип до 'square' | 'rectangle' | 'circle', но вызовы типа fetch<Square>('circle', 'some-id') все равно будут компилироваться.Как решить эту проблему?Есть ли способ определить функцию, например, одну из следующих версий?

  1. fetch<T extends Shape>(type: T['kind'], id: string):T
  2. fetch<T extends Shape['kind']>(type: T, id: string): SomeMagic<T> и SomeMagic<T> помогает компилятору найти правильный тип, например SomeMagic<'square'>выводит Square во время компиляции?

Ответы [ 2 ]

0 голосов
/ 23 декабря 2018

Первое, с чем нам нужно иметь дело, это связать фигуры с их соответствующими видами.Ваше оригинальное решение:

/**
 * Bad — doesn't bind the `Shape` with its `kind`. It allows calling `fetch<Square>('circle', 'foo')`.
 */
declare function fetch<T extends Shape>(type: Shape['kind'], id: string): T;

Вместо этого скажите TypeScript, что type должен быть не просто какого-либо вида, а именно того вида, который мы сейчас рассматриваем.

/**
 * Better: the `kind` is bound with its corresponding `Shape`. The downside: The exact return type is not inferred.
 */
declare function fetch<T extends Shape>(type: T['kind'], id: string): T;

const oups = fetch<Square>('circle', 'foo'); // $ExpectError
const shape = fetch('circle', 'foo');        // $ExpectType Shape

Это лучше, но тип возвращаемого значения - Shape.Мы можем добиться большего, указав перегрузки для вашей функции:

/**
 * Using overloads can help you determine the exact return type.
 */
declare function fetch(type: 'circle', id: string): Circle;
declare function fetch(type: 'square', id: string): Square;
declare function fetch(type: 'rectangle', id: string): Rectangle;

const circle = fetch('circle', 'foo'); // $ExpectType Circle

Это даст вам точные типы возврата, но за счет необходимости писать больше кода.Можно также утверждать, что существует некоторая избыточность - связь между формой и ее видом уже инкапсулирована в ее интерфейсе, поэтому повторение ее в виде перегрузки кажется менее совершенным.

0 голосов
/ 23 декабря 2018

Я бы порекомендовал ваш подход "версии 2" следующим образом:

declare function fetch<T extends Shape['kind']>(
  type: T, 
  id: string
): Extract<Shape, {kind: T}>;
fetch<'square'>('circle', 'some-id'); //error
const shape = fetch('rectangle', 'some-id'); // shape is a Rectangle

"Волшебство" - это функция типа Extract, определенная в стандартной библиотеке , которая условный тип , который извлекает совпадающие элементы из объединения.

Вы могли бы использовать ваш подход "версии 1", но я не рекомендую его:

declare function fetch<T extends Shape>(type: T['kind'], id: string): T;
fetch<Square>('circle', 'some-id'); //error
const shape = fetch('rectangle', 'some-id'); // shape is a Shape

Обратите внимание, что параметр type равен T['kind'], а не Shape['kind'].Это решает вашу проблему, как указано, но если вы позволите компилятору выводить T из параметров, он в итоге просто выведет Shape, поскольку T['kind'] не является отличным сайтом вывода для T.

В любом случае, надеюсь, это поможет.Удачи.

...