Typescript 泛型与返回类型不兼容

Posted

技术标签:

【中文标题】Typescript 泛型与返回类型不兼容【英文标题】:Typescript generic incompatible with return type 【发布时间】:2019-09-06 12:12:19 【问题描述】:

我遇到了 typescript 泛型问题:

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

游乐场网址: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'string'%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%20return%20a.concat(b)%0D%0A%20%20%20%20%7D%0D%0A%20%20% 20%20return%20a%20%2B%20b%0D%0A%7D%0D%0A

打字似乎合适,但我有一些错误。打字稿泛型似乎有些混乱,但我找到的答案都没有帮助我解决这个基本用例。

【问题讨论】:

您缺少演员表:return a.concat(b) as T。还有:return Number(a) + Number(b) as T 【参考方案1】:

TypeScript 不会通过控制流来缩小泛型类型(请参阅microsoft/TypeScript#24085)。所以即使a 的类型已知为stringT 的类型仍将顽固地保持T。使您的代码按原样编译的唯一方法是使用type assertions 来安抚编译器(如 cmets 中所述):

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 将prompt 编译器将T 推断为string literal type 或numeric literal type(如果可以)。当您将像"a" 这样的字符串文字作为参数传递时,T 将缩小为"a",这意味着T 只是字符串"a",没有其他值。我猜你不想要那个。

您正在创建的功能类型是您传统上(无论如何在 TS2.8 之前)使用overloads 来完成的功能:

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

可以使用泛型和conditional types 获得相同的行为,但这可能不值得:

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

【讨论】:

以上是关于Typescript 泛型与返回类型不兼容的主要内容,如果未能解决你的问题,请参考以下文章

泛型与反射:type接口来历及子接口

协变逆变与不变:数组泛型与返回类型

Java泛型与集合笔记

泛型与反射

TypeScript 中泛型类型的子类型推断

java泛型与通配符