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"]
?
我也希望在制作这样的中间件功能时能够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 },
})
Strategy
- 创建哈希映射数据结构,其中键是操作名称,值是函数的 return 类型。
此外,我有 overloaded doAction
函数来缩小 return 类型。
假设我有一个联合类型,我正在使用类似 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"]
?
我也希望在制作这样的中间件功能时能够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 },
})
Strategy
- 创建哈希映射数据结构,其中键是操作名称,值是函数的 return 类型。
此外,我有 overloaded doAction
函数来缩小 return 类型。