我正在try 向这个WebSocket协议添加一些类型:

type Action =
    | {
        action: "change-or-create-state";
        response: string;
    }
    | {
        action: "get-state";
        response: string | null;
    };

/**
 * map an action to its response
 */
type ActionResponse<T extends Action["action"]> = Extract<
    Action,
    { action: T }
>["response"];

export async function doAction<
    TAction extends Action["action"]
>(action: TAction): Promise<ActionResponse<TAction>> { /* ... */ }

我对那些实际上有resolving个promise 的类型有困难,虽然:

// in reality received over websocket
const someDataFromAPI: string = '"hello world"'

export async function doAction<
    TAction extends Action["action"]
>(action: TAction): Promise<ActionResponse<TAction>> {
    return new Promise<ActionResponse<TAction>>((resolve) => {
        // I know I could just keep this as 'any' -- the real code does validation
        // which narrows the type.
        const jsonWithSpecificType: ActionResponse<TAction> = JSON.parse(someDataFromAPI);

        /** ⚠️
        Argument of type 'ActionResponse<TAction>' is not assignable to parameter of type 'ActionResponse<TAction> | PromiseLike<ActionResponse<TAction>>'.
        Type 'Extract<{ action: "get-state"; response: string | null; }, { action: TAction; }>["response"]' is not assignable to type 'ActionResponse<TAction> | PromiseLike<ActionResponse<TAction>>'.
        Type 'string | null' is not assignable to type 'ActionResponse<TAction> | PromiseLike<ActionResponse<TAction>>'.
        Type 'null' is not assignable to type 'ActionResponse<TAction> | PromiseLike<ActionResponse<TAction>>'.(2345)
        */
        resolve(jsonWithSpecificType)
    })
}

Playground link here.我遗漏了什么?显然,any在这里可以很好地工作,但我只是好奇问题是什么.我的猜测是,它不能保证TAction在箭头函数内部的类型与在外部函数中的类型相同?

推荐答案

我能找到的最接近于这个问题的标准答案是microsoft/TypeScript#47233,这与这里的问题类似,并被归类为错误.这could意味着这是一个错误,但它也可能只是一个设计限制.如果我有时间,我会打开一期新的,得到一个权威的答案.

我强烈怀疑这里的问题是类型ActionResponse<TAction>ActionResponse<TAction> | PromiseLike<ActionResponse<TAction>>都是generic,因此编译器不能完全计算它们.由于ActionResponse<T>涉及一些类型操作,如union typesconditional typesindexed accesses),因此ActionResponse<TAction>ActionResponse<TAction> | PromiseLike<ActionResponse<TAction>>相当复杂,可能未被编译器计算.因此,它们有些"不透明".

人们可以通过判断看到,前者必须是可分配给后者的,这是通过规则"X总是可分配给X | Y"和模式匹配ActionResponse<T>XPromiseLike<ActionResponse<TAction>>Y来实现的.但是编译器不是人,如果它在考虑联合赋值规则之前放弃计算类型,那么您就会得到一个错误.

我认为这种行为的最小例子是这样的

type F<K extends "a" | "b"> = (
    ({ x: "a"; y: "a"; } | { x: "b"; y: "b"; }) & { x: K }
)["y"];

function foo<K extends "a" | "b">(fk: F<K>): F<K> | undefined {
    return fk; // error?!
}

这对任何人来说都是显而易见的,无论F<K>是什么,F<K>都是可赋值给F<K> | undefined的,但编译器无法看到它,因为F的定义中进行了复杂的联合区分和索引.我在这里使用intersection而不是Extract,但这是相同的基本运算.

就目前而言,这个答案只是我对正在发生的事情最有见地的猜测.如果我提交了一个错误,并得到了一个明确的答案,我会回来编辑以反映它.

Playground link to code

Typescript相关问答推荐

为什么缩小封闭内部不适用于属性对象,但适用于声明的变量?

对深度对象键/路径进行适当的智能感知和类型判断,作为函数参数,而不会触发递归类型限制器

异步动态生成Playwright测试,显示未找到测试

泛型函数中输入参数类型的推断问题

TypeScrip表示该类型不可赋值

嵌套对象的类型

是否可以判断某个类型是否已合并为内部类型?

以Angular 执行其他组件的函数

React router 6(数据路由)使用数据重定向

RXJS将对键盘/鼠标点击组合做出react

界面中数组中的混合类型导致打字错误

如何在具有嵌套数组的接口中允许新属性

编剧- Select 动态下拉选项

基于属性值的条件类型

T的typeof键的Typescript定义

如何让Record中的每个值都有独立的类型?

我可以在 Typescript 中重用函数声明吗?

如何为字符串模板文字创建类型保护

在对象类型的类型别名中,属性是用分号 (;) 还是逗号 (,) 分隔的?

当受其他参数限制时,通用参数类型不会缩小