Типовой сценарий несовместим с возвращаемым типом

У меня проблема с обобщениями машинописного текста:

function isString(a: any): a is string {
    return typeof a === 'string'
}

function concat<T extends string | number>(a: T, b: T): T {
    if (isString(a) && isString(b)) {
        return a.concat(b)
    }
    return a + b
}

URL игровой площадки: https://www.typescriptlang.org/play/index.html#src=function%20isString(a%3A%20any)%3A%20a%20is%20string%20%7B%0D%0A%20%20%20%20return%20typeof%20a%20%3D%3D%3D%20 'строка'% 0D% 0A% 7D% 0D% 0A% 0D% 0Afunction% 20concat% 3CT% 20extends% 20string% 20% 7C% 20number% 3E (a% 3A% 20T% 2C% 20b% 3A% 20T) % 3A% 20T% 20% 7B% 0D% 0A% 20% 20% 20% 20if% 20 (isString (a)% 20% 26% 26% 20isString (b))% 20% 7B% 0D% 0A% 20% 20% 20% 20% 20% 20% 20% 20 возврат% 20a.concat (b)% 0D% 0A% 20% 20% 20% 20% 7D% 0D% 0A% 20% 20% 20% 20 возврат% 20a% 20 % 2B% 20b% 0D% 0A% 7D% 0D% 0A

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


person Dispix    schedule 16.04.2019    source источник
comment
Вам не хватает состава: return a.concat(b) as T. А также: return Number(a) + Number(b) as T   -  person haim770    schedule 16.04.2019


Ответы (1)


TypeScript не сужает общие типы через поток управления (см. microsoft / TypeScript # 24085). Таким образом, даже если известно, что тип a string, тип T будет упорно оставаться T. Единственный способ заставить ваш код компилироваться как есть - использовать введите assertions, чтобы успокоить компилятор (как указано в комментариях):

function concat<T extends string | number>(a: T, b: T): T {
    if (isString(a) && isString(b)) {
        return a.concat(b) as T; // assert as T
    }
    return (a as number) + (b as number) as T; // assert as numbers and T
}

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

// string literal types
const oops1 = concat("a", "b");
// type "a" | "b" at compile time, but "ab" at runtime

// numeric literal types
const oops2 = concat(5, 6); 
// type 5 | 6 at compile time, but 11 at runtime

// string | number types
let notSure = Math.random() < 0.5 ? "a" : 1 
const oops3 = concat(notSure, 100); // no error
// I bet you didn't want concat() to possibly accept string + number

Самая большая проблема заключается в том, что T extends string | number предложит компилятору вывести T как строковый литерал или числовой буквальный тип, если можно. Когда вы передаете строковый литерал, например "a", в качестве параметра, T будет сужен до "a", что означает, что T - это только строка "a" и никакое другое значение. Я полагаю, ты этого не хочешь.

Тип выполняемой вами функции - это то, что вы традиционно (до TS2.8 в любом случае) использовали бы перегрузки для выполнения:

function concat(a: string, b: string): string;
function concat(a: number, b: number): number;
function concat(a: string | number, b: string | number): string | number {
    if (isString(a) && isString(b)) {
        return a.concat(b);
    }
    return (a as number) + (b as number);
}

Теперь эти примеры будут вести себя так, как вы ожидаете:

const oops1 = concat("a", "b"); // string
const oops2 = concat(5, 6); // number
let notSure = Math.random() < 0.5 ? "a" : 1 
const oops3 = concat(notSure, 100); // error, notSure not allowed

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

type StringOrNumber<T extends string | number> =
    [T] extends [string] ? string :
    [T] extends [number] ? number : never

function concat<T extends string | number>(
    a: T,
    b: StringOrNumber<T>
): StringOrNumber<T> {
    if (isString(a) && isString(b)) {
        return a.concat(b) as any;
    }
    return (a as number) + (b as number) as any;
}

const oops1 = concat("a", "b"); // string
const oops2 = concat(5, 6); // number
let notSure = Math.random() < 0.5 ? "a" : 1
const oops3 = concat(notSure, 100); // error
person jcalz    schedule 16.04.2019