typescript 依赖于对象参数的类型脚本返回类型

ycl3bljg  于 2023-01-27  发布在  TypeScript
关注(0)|答案(2)|浏览(143)

我如何根据一个函数正确地推断返回类型,该函数有一个参数是两种类型的并集?
我已经尝试了以下条件类型,但它不工作(见inline comment的typescript错误):
打字机游戏场

type Status = 'statusType'
type GoalActivity = 'goalActivityType'

type Argument = { type: 'status'; status: Status | null } | { type: 'goalActivity'; goalActivity: GoalActivity | null }

const handleReaction = (arg: Argument): Argument extends { type: "status" } ? Status : GoalActivity => {
    if (arg.type === 'status') {
        return 'statusType' // Type '"statusType"' is not assignable to type '"goalActivityType"'.
    } else {
        return 'goalActivityType'
    }
}

我也尝试过使用一种箭头函数的函数重载形式(as described here),但这也会导致TypeScript错误,而且使用"any"会失去函数定义中的大部分类型优势:
打字机游戏场

type Status = 'statusType'
type GoalActivity = 'goalActivityType'

type HandleReaction = {
    (arg: { type: 'status'; status: Status | null }): Status
    (arg: { type: 'goalActivity'; goalActivity: GoalActivity | null }): GoalActivity
}

const handleReaction: HandleReaction = (arg: any) => { // Type '"goalActivityType"' is not assignable to type '"statusType"'.
    if (arg.type === 'status') {
        return 'statusType'
    } else {
        return 'goalActivityType'
    }
}

这个问题类似于this one,但不同之处在于函数参数是一个对象。

ssgvzors

ssgvzors1#

问题

第一件事是你没有为你的参数使用泛型类型,这将导致typescript永远不会根据你的输入推断出正确的类型(你可以想象泛型类型是参数,tsc需要它根据你的输入计算结果)。
简而言之
const handleReaction = (arg: Argument): Argument extends { type: "status" } ? Status : GoalActivity => { // ... }
将始终返回Status | GoalActivity作为返回类型。

溶液

当然,这里你必须使用泛型类型作为你的参数,我将用行内解释来拆分你的代码:

type Status = 'statusType'
type GoalActivity = 'goalActivityType'

type StatusObj = { type: 'status'; status: Status | null };
type GoalActivityObj = { type: 'goalActivity'; goalActivity: GoalActivity | null }

type Argument = StatusObj | GoalActivityObj;

// Define returned type based on a input argument `T`
type ReturnType<T> = T extends StatusObj ? Status : GoalActivity;

// Generic type should be used here
const handleReaction = <T extends Argument>(arg: T): ReturnType<T> => {
    if (arg.type === 'status') {

        // Q: Why do we have to cast here?
        // A: Any returned type can't assign to statement of `type ReturnType<T> ...`
        // but luckily `tsc` still allows us to cast back since they are all string literal
        return 'statusType' as ReturnType<T>;

    } else {
        return 'goalActivityType' as ReturnType<T>;
    }
}
x8goxv8g

x8goxv8g2#

你的参数对象应该有自己的类型。一旦它们有了类型,你就可以像这样使用函数重载了:

type Status = { type: 'status', status: 'statusValue' };
type GoalActivity = { type: 'goalActivity', goalActivity: 'goalValue' };

function handleReaction(arg: Status): Status['status']
function handleReaction(arg: GoalActivity): GoalActivity['goalActivity']
function handleReaction(arg: Status | GoalActivity) { 
    if ('status' in arg) {
        return arg.status;
    } else {
        return arg.goalActivity;
    }
}

const a = handleReaction({ type: 'status', status: 'statusValue' })
//    ^? 'statusValue'
const b = handleReaction({ type: 'goalActivity', goalActivity: 'goalValue' })
//    ^? 'goalValue'

Playground

相关问题