#typescript #generics #typescript-typings #typescript-generics
#typescript #дженерики #typescript-типизации #typescript-generics
Вопрос:
У меня возникла проблема с typescript generics:
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) amp;amp; isString(b)) {
return a.concat(b)
}
return a b
}
URL-адрес игровой площадки: https://www.typescriptlang.org/play/index.html#src=function isString(a: any): a is string {
return typeof a === ‘string’
}
function concat(a: T, b: T): T {
if (isString(a) && isString(b)) {
return a.concat(b)
}
return a + b
}
Ввод текста кажется подходящим, но у меня есть некоторые ошибки. Кажется, есть некоторые недоразумения с typescript generics, но ни один из ответов, которые я нашел, не помог мне с этим базовым вариантом использования.
Комментарии:
1. Вам не хватает приведения :
return a.concat(b) as T
. А также:return Number(a) Number(b) as T
Ответ №1:
TypeScript не сужает общие типы с помощью потока управления (см. microsoft / TypeScript #24085). Таким образом, даже если известно, что тип a
известен string
, тип T
будет упорно сохраняться T
. Единственный способ заставить ваш код компилироваться как есть — это использовать утверждения типа для успокоения компилятора (как указано в комментариях):
function concat<T extends string | number>(a: T, b: T): T {
if (isString(a) amp;amp; 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"
in, в качестве параметра, 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) amp;amp; 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) amp;amp; 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