Сужение типов не работает должным образом с общими ограничениями

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

function somefunc<T extends string | number>(input: T): T {
  if (typeof input === "string") {
    // expecting input to be of type "string"
    // but input becomes of type T & "string"
    input
  } else {
    // expecting input to be of type "number"
    // but input becomes of type T extends string | number
    input
 }
}

Если я откажусь от универсальных шаблонов и просто аннотирую аргумент функции как string | number, он работает, но для моего варианта использования мне нужны общие ограничения.

Изменить

Вариант использования - это, по сути, попытка также использовать это с условными типами. В основном я хочу, чтобы тип результата был условным типом, который зависит от типа ввода. Поэтому, когда тип ввода number, результат также number, когда ввод string, результат также становится string. В основном это:

type Result<T> = T extends string ? string : number

function somefunc<T extends string | number>(input: T): Result<T> {
  if (typeof input === "string") {
    // expecting input to be of type "string"
    // but input becomes of type T & "string"
    input
  } else {
    // expecting input to be of type "number"
    // but input becomes of type T extends string | number
    input
 }
}
 

Я, вероятно, что-то упускаю, но вопрос в том, как мне использовать универсальное ограничение на основе объединения и работать с сужением типа, как я ожидал. В приведенном выше коде это будет означать, что в ветке if input становится типом string, а в ветке else он становится number (или, по крайней мере, становится T & number)

** Редактировать **

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


person Finlay Weber    schedule 14.01.2021    source источник
comment
Это потому, что с универсальными шаблонами ваш somefunc метод будет принимать все, что угодно, кроме строковых или числовых типов. Если вы знаете, что input должен состоять из строки или числа, разве вы не можете просто сделать function somefunc(input: string | number): string | number?   -  person Terry    schedule 14.01.2021
comment
обновил вопрос, чтобы предоставить дополнительную информацию   -  person Finlay Weber    schedule 14.01.2021
comment
Если ветвь else становится типа T & number, то это все равно идти, аналогично тому, как ветвь if имеет тип T & string. но прямо сейчас иначе T расширяет строку | количество   -  person Finlay Weber    schedule 14.01.2021


Ответы (2)


Причина, по которой он не сужается, объясняется в этом ответе

Хакерский способ правильно сузить тип:

type Result<T> = T extends string ? string : number;

function somefunc<T extends string | number>(input: T): Result<T> {
  const inputNarrowed: string | number = input;

  if (typeof inputNarrowed === "string") {
    inputNarrowed; // string
  } else {
    inputNarrowed; // number
  }

  return inputNarrowed as Result<T>;
}

Альтернативное решение (которое я предпочитаю) с перегрузкой + условным общим

type Result<T> = T extends string ? string : number;

function somefunc<T extends string | number>(input: T): Result<T>;
function somefunc(input: string | number) {
  if (typeof input === "string") {
    input; // string
  } else {
    input; // number
  }

  return input;
}

const str = somefunc("string"); // string
const num = somefunc(1); // number
person Owl    schedule 14.01.2021
comment
ха-ха! Это злобно ... Но работает! :) - person Finlay Weber; 14.01.2021
comment
Я должен был сделать несколько приведений в возвращаемом выражении ... например, return inputNarrowed as Result<T>; так и должно быть? - person Finlay Weber; 14.01.2021
comment
Да, однако это не связано с inputNarrowed. Простая функция, такая как const fun = <T extends string | number>(input: T): Result<T> => input;, по-прежнему требует, чтобы вы приводили возвращаемый тип, потому что тип возврата - Result<T>, а не T - person Owl; 14.01.2021
comment
Я добавил альтернативное решение, которое может вас заинтересовать, оно по-прежнему использует перегрузку, но также использует универсальный тип для интерфейса функции. - person Owl; 14.01.2021

Если вы хотите сузить до точного типа, рассмотрите следующий пример:

function somefunc(input: number): number
function somefunc(input: string): string
function somefunc(input: string | number): string | number {
  if (typeof input === "string") {
    return input // stirng
  } else {
    return input // number
  }
}


const x = somefunc(10)

Недостаток: нет дженериков)

person captain-yossarian    schedule 14.01.2021
comment
Ага. У меня уже работает перегрузка функций ... хотел посмотреть, возможно ли это с комбинацией универсальных и условных типов :) - person Finlay Weber; 14.01.2021
comment
Кстати, вы можете позвонить input.toString(), чтобы сделать это строго string вместо T & string - person captain-yossarian; 14.01.2021