Определить тип возвращаемого значения функции в словаре на основе ее параметра в TypeScript

#typescript

#typescript

Вопрос:

У меня есть функция, которая должна возвращать разные объекты в зависимости от параметра функции. Есть ли способ, как я могу добиться этого в Typescript?

Вот как далеко я продвинулся:

 type returnMap = {
    'a': number,
    'b': string[]
}

function func<T extends returnMap, K extends keyof T>(val: K): T[K] {
    if (val == 'a') {return 5}
    if (val == 'b') {return ['a', 'b']}
    return 42
}
console.log(func('a'))  // 5
 

Однако в возвращаемых значениях говорится: «TS2322: тип ‘5’ не может быть присвоен типу ‘T[K]'».

Заранее благодарю

Ответ №1:

Это сделает:

 interface ReturnMap {
  'a': number;
  'b': string[];
}

function func<K extends keyof ReturnMap>(val: K): ReturnMap[K];
function func(val: keyof ReturnMap): ReturnMap[keyof ReturnMap] {
  if (val === 'a') return 5;
  if (val === 'b') return ['a', 'b'];
  throw new Error();
}

const r1 = func('a'); // const r1: number
const r2 = func('b'); // const r2: string[]

// Argument of type '"c"' is not assignable to parameter of type '"a" | "b"'.ts(2345)
const r3 = func('c'); // const r3: number | string[]
 

Обратите внимание на синтаксис перегрузки функции:

 function func<K extends keyof ReturnMap>(val: K): ReturnMap[K];
function func(val: keyof ReturnMap): ReturnMap[keyof ReturnMap] { // ...
 

Если бы вы использовали только вторую подпись, то возвращаемый тип был бы всегда number | string[] .

Если вы использовали только первую подпись, то ReturnMap[K] вычисляется (внутри тела функции) как number amp; string[] контравариантный, поэтому вы получаете ошибки, которые вы описали в своем вопросе выше.

В качестве альтернативы, если вы не хотите использовать перегруженную сигнатуру — вы можете привести результат внутри тела функции:

 function func<K extends keyof ReturnMap>(val: K): ReturnMap[K] {
  if (val === 'a') return 5 as ReturnMap[K];
  if (val === 'b') return ['a', 'b'] as ReturnMap[K];
  throw new Error();
}
 

Комментарии:

1. Большое вам спасибо, Томаш. Это действительно работает! Не знал, что существует что-то вроде перегрузки функций или что это может решить мою проблему.

2. Привет @Tomasz Gawel, я не могу воспроизвести If you only used first signature, then ReturnMap[K] is evaluated as number amp; string[] . Не могли бы вы, пожалуйста, сказать мне, как это воспроизвести?

3. @captain-yossarian я имел в виду внутри тела функции.

4. Для меня это не выглядит доказательством будущего. Хотя это работает для OP. Из вопроса кажется, что OP хочет иметь возвращаемый тип, который соответствует K extends T[keyof T] where T extends ReturnValue . таким образом, возвращаемый тип может быть не только number | string[] , но он также должен содержать все типы значений в T extends ReturnValue

5. @Nishant При таком подходе вы могли бы написать общую подпись для любого типа (я имею в виду «return map», передаваемую как общий параметр), но поскольку это реализация функции, вы не могли бы предоставить фактическую реализацию для любого типа ;).

Ответ №2:

Я думаю, что перегрузка может вам помочь:

 type returnMap = {
    'a': number,
    'b': string[],
    'x': 42,
    'z': 42,
}

function func<T extends returnMap, K extends Exclude<keyof returnMap,'a'|'b'>>(val: K): 42;
function func<T extends returnMap, K extends 'b'>(val: K): string[];
function func<T extends returnMap, K extends 'a'>(val: K): number;
function func<T extends returnMap, K extends keyof T>(val: K): number | string[] {
    if (val == 'a') {
        return 5
    }
    if (val == 'b') {
        return ['a', 'b']
    }
    return 42
}

const result = func('a') // number
const result1 = func('b') // string[]
const result2 = func('x') // 42
const result3 = func('z') // 42
 

Комментарии:

1. Это тоже работает, но кажется немного более сложным, чем решение Tomasz. Однако для этого может быть причина (?)

2. Решение @Nayaro Tomasz немного лучше, потому что оно более общее, чем мое. Не стесняйтесь использовать его решение. Просто убедитесь, что вы знаете о перегрузках функций typescriptlang.org/docs/handbook/functions.html#overloads

Ответ №3:

Это кажется хорошим вариантом использования для использования any в качестве типа возвращаемой переменной.

См. https://www.typescriptlang.org/docs/handbook/basic-types.html#any .

Комментарии:

1. Я думаю, что мыслительный процесс в вопросе T[K] заключается number | string[] в том, что. Итак, почему компилятор жалуется, когда код возвращается number | string[] .

2. Это мое текущее решение для решения этой проблемы. Тем не менее, я хотел бы обеспечить больше удобства и безопасности типов. Вариант использования заключается в том, что этот метод выполняет некоторые вызовы API и возвращает разные объекты в зависимости от URL, который здесь указан как val .

3. any считается плохой практикой. Есть только несколько случаев, когда это может помочь, и я ничего о них не знаю)