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
return a.concat(b) as T
. А также:return Number(a) + Number(b) as T
- person haim770   schedule 16.04.2019