TypeScript не сужает общие типы с помощью потока управления . Таким образом, даже если тип a
, как известно, string
, тип T
будет упорно остаются T
. Единственный способ сделать ваш код компилируемым как есть - это использовать утверждения типа для успокоения компилятора (как упомянуто в комментариях):
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
В любом случае, надеюсь, это поможет. Удачи!