TypeScript 中联合类型的相应子类型的泛型

Generics for corresponding sub-types of union types in TypeScript

假设我有一个联合类型,我正在使用类似 reducer 的模式进行 API 调用,看起来像这样:

type Action = {
    request: {
        action: "create user",
        payload: { name: string, age: number },
    },
    response: { ok: true, message: "user created" },
} | {
    request: {
        action: "delete user",
        payload: { id: number },
    },
    response: { ok: true, message: "user deleted" },
};

当我选择一个特定的动作时,我希望能够预测相应的反应是什么类型安全。例如,如果我要创建这样的函数,我想强制我确实为每个请求返回正确的相应响应。我在输入类型的类型安全方面没有问题。我在将输出link输出到与输入类型相关的相应子类型时遇到问题。

function doAction<T extends Action>(request: T["request"]): T["response"] {
    if (request.action === "create user") {
        const userName = payload.user.name; // Here we're good for type safety 
        return {
            ok: true,
            message: "user deleted", // This should error, but it doesn't 
        };
    } else {
        const { id } = request.payload; // Again, we're good here for type safety 
        return {
            ok: true,
            message: "user deleted", // Ok, but missing strong type safety here  
        }; 
    }
}

如何表明我想要 ["request"] 中选择的对象类型的 ["response"] 部分,而不仅仅是任何 Action["response"]

TypeScript Playground Link

我也希望在制作这样的中间件功能时能够link对象的这两半,例如:

function<T extends Action>(req: Request<T["request"]>, res: Response<T["response"]>) {
    if (req.body.action === "create user") {
        res.send({ ok: true, message: "user deleted" }); // this should not be allowed
    }
    ...

为了降低复杂性并提高类型安全性,值得在此处使用 strategy pattern

考虑这个例子:

type Action = {
    request: {
        action: "create user",
        payload: { name: string, age: number },
    },
    response: { ok: true, message: "user created" },
} | {
    request: {
        action: "delete user",
        payload: { id: number },
    },
    response: { ok: true, message: "user deleted" },
};

type Strategy = {
    [Prop in Action['request']['action']]: Extract<Action, { request: { action: Prop } }>['response']
}

const strategy: Strategy = {
    'create user': { ok: true, message: "user created" },
    'delete user': { ok: true, message: "user deleted" }
};

function doAction<T extends Action['request']>(request: T): Extract<Action, { request: T }>['response']
function doAction(request: Action['request']) {
    return strategy[request.action]

}

// {
//     ok: true;
//     message: "user created";
// }
const createUser = doAction({
    action: "create user",
    payload: { name: 'string', age: 42 },
})


// {
//     ok: true;
//     message: "user deleted";
// }
const deleteUser = doAction({
    action: "delete user",
    payload: { id: 1 },
})

Playground

Strategy - 创建哈希映射数据结构,其中键是操作名称,值是函数的 return 类型。

此外,我有 overloaded doAction 函数来缩小 return 类型。