【问题标题】:Define return type of a function in dictionary based on its parameter in TypeScript根据 TypeScript 中的参数定义字典中函数的返回类型
【发布时间】:2021-01-08 07:21:35
【问题描述】:

我有一个函数,它会根据函数参数返回不同的对象。 有没有办法在 Typescript 中实现这一点?

这是我已经走了多远:

type returnMap = {
    'a': number,
    'b': string[]
}

function func<T extends returnMap, K extends keyof T>(val: K): T[K] {
    if (val == 'a') {return 5}
    if (val == 'b') {return ['a', 'b']}
    return 42
}
console.log(func('a'))  // 5

但是,它在返回值处显示“TS2322:类型 '5' 不可分配给类型 'T[K]'”。

提前谢谢你

【问题讨论】:

    标签: typescript


    【解决方案1】:

    这样就可以了:

    interface ReturnMap {
      'a': number;
      'b': string[];
    }
    
    function func<K extends keyof ReturnMap>(val: K): ReturnMap[K];
    function func(val: keyof ReturnMap): ReturnMap[keyof ReturnMap] {
      if (val === 'a') return 5;
      if (val === 'b') return ['a', 'b'];
      throw new Error();
    }
    
    const r1 = func('a'); // const r1: number
    const r2 = func('b'); // const r2: string[]
    
    // Argument of type '"c"' is not assignable to parameter of type '"a" | "b"'.ts(2345)
    const r3 = func('c'); // const r3: number | string[]
    

    注意函数重载语法:

    function func<K extends keyof ReturnMap>(val: K): ReturnMap[K];
    function func(val: keyof ReturnMap): ReturnMap[keyof ReturnMap] { // ...
    

    如果您只使用第二个签名,则返回的类型将始终为 number | string[]

    如果您只使用第一个签名,则 ReturnMap[K] 被评估(在函数体内)为逆变的 number &amp; string[],因此您会得到您在上面的问题中描述的错误。

    或者,如果您不想使用重载签名 - 您可以将结果投射到函数体中:

    function func<K extends keyof ReturnMap>(val: K): ReturnMap[K] {
      if (val === 'a') return 5 as ReturnMap[K];
      if (val === 'b') return ['a', 'b'] as ReturnMap[K];
      throw new Error();
    }
    

    【讨论】:

    • 非常感谢托马斯。这确实有效!不知道存在函数重载之类的东西,或者这可以解决我的问题。
    • 嗨@Tomasz Gawel,我无法复制If you only used first signature, then ReturnMap[K] is evaluated as number &amp; string[]。你能告诉我如何重现它吗?
    • @captain-yossarian 我的意思是在函数体内。
    • 对我来说它看起来不像未来的证据。虽然,它适用于 OP。从这个问题来看,OP 似乎希望有一个返回类型可以容纳K extends T[keyof T],其中T extends ReturnValue。所以返回类型可能不仅仅是number | string[],它还应该包含T extends ReturnValue中的所有类型的值
    • @Nishant 使用这种方法您可以为任何类型编写通用签名(我的意思是作为通用参数传递的“返回映射”),但由于这是函数实现,您无法为任何类型提供实际实现; )。
    【解决方案2】:

    我认为重载可能会对你有所帮助:

    type returnMap = {
        'a': number,
        'b': string[],
        'x': 42,
        'z': 42,
    }
    
    function func<T extends returnMap, K extends Exclude<keyof returnMap,'a'|'b'>>(val: K): 42;
    function func<T extends returnMap, K extends 'b'>(val: K): string[];
    function func<T extends returnMap, K extends 'a'>(val: K): number;
    function func<T extends returnMap, K extends keyof T>(val: K): number | string[] {
        if (val == 'a') {
            return 5
        }
        if (val == 'b') {
            return ['a', 'b']
        }
        return 42
    }
    
    const result = func('a') // number
    const result1 = func('b') // string[]
    const result2 = func('x') // 42
    const result3 = func('z') // 42
    

    【讨论】:

    • 这也有效,但似乎比 Tomasz 的解决方案复杂一些。但是,这可能是有原因的(?)
    • @Nayaro Tomasz 的解决方案要好一些,因为它比我的更通用。随意使用他的解决方案。只要确保你知道函数重载typescriptlang.org/docs/handbook/functions.html#overloads
    【解决方案3】:

    这似乎是使用 any 作为返回变量类型的一个很好的用例。

    https://www.typescriptlang.org/docs/handbook/basic-types.html#any

    【讨论】:

    • 我认为问题中的思考过程是T[K]number | string[]。那么,为什么当代码返回number | string[]时编译器会报错。
    • 这是我目前解决这个问题的解决方案。但是,我想提供更多的便利和类型安全。用例是此方法进行一些 API 调用并根据此处以 val 给出的 URL 返回不同的对象。
    • any 被认为是一种不好的做法。只有少数情况可能会有所帮助,我对它们一无所知)
    猜你喜欢
    • 2020-11-13
    • 1970-01-01
    • 2021-01-10
    • 1970-01-01
    • 2021-04-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-08
    相关资源
    最近更新 更多