TypeScript中如何根据Action参数推断函数返回类型?
TypeScript 根据Action参数返回对应类型结果的实现问题
需求描述
实现一个接收"DELETE"|"RENAME"|"MOVE"类型Action参数的函数,根据传入的Action返回对应结构的结果:
"DELETE"操作返回含deleteRecursively: boolean的对象"RENAME"操作返回含newName: string | null的对象"MOVE"操作返回含newLocation: string | null的对象
已实现代码
type TAction = 'RENAME' | 'DELETE' | 'MOVE'; type DeleteActionResult = { deleteRecursively: boolean }; type RenameActionResult = { newName: string | null }; type MoveActionResult = { newLocation: string | null }; type ActionResult<T extends TAction> = T extends 'DELETE' ? DeleteActionResult : T extends 'RENAME' ? RenameActionResult : T extends 'MOVE' ? MoveActionResult : never; type TOnAction = <T extends TAction>(action: T, file: File) => Promise<ActionResult<T>>; const user = { role: "admin" }; const onAction: TOnAction = async (action, file) => { switch (action) { case 'DELETE': { if (user.role === 'admin') { return { deleteRecursively: true }; // 类型无法推断 } return {deleteRecursively: false}; // 类型无法推断 } case 'RENAME': { const newName = window.prompt('Enter new name:', file.name); return { newName }; // 类型无法推断 } case 'MOVE': { const newLocation = window.prompt('Enter new location:'); return { newLocation }; // 类型无法推断 } default: throw new Error('Invalid action'); } }; const handleDelete = async (file: File){ const shouldDeleteRecursively = await onAction("DELETE", file) ?? false; await fetch(".../delete", {deleteRecursively: shouldDeleteRecursively }); }
遇到的错误
Type '<T extends TAction>(action: T, file: File) => Promise<DeleteActionResult | RenameActionResult | MoveActionResult>' is not assignable to type 'TOnAction'. Type 'Promise<DeleteActionResult | RenameActionResult | MoveActionResult>' is not assignable to type 'Promise<ActionResult<T>>'. Type 'DeleteActionResult | RenameActionResult | MoveActionResult' is not assignable to type 'ActionResult<T>'. Type 'DeleteActionResult' is not assignable to type 'ActionResult<T>'.
问题
调用该函数时类型可正常推断,但在switch分支内部返回值无法被正确推断,导致类型不匹配报错,请问是否可以实现该需求?
解决方案
可以实现该需求,问题根源在于TypeScript无法将switch分支的具体返回类型与泛型T建立关联,以下是两种可行的解决方式:
方法1:使用函数重载替代泛型条件类型
函数重载能更清晰地定义每个Action参数对应的返回类型,让TypeScript准确推断分支返回值:
type TAction = 'RENAME' | 'DELETE' | 'MOVE'; type DeleteActionResult = { deleteRecursively: boolean }; type RenameActionResult = { newName: string | null }; type MoveActionResult = { newLocation: string | null }; // 定义函数重载签名 async function onAction(action: 'DELETE', file: File): Promise<DeleteActionResult>; async function onAction(action: 'RENAME', file: File): Promise<RenameActionResult>; async function onAction(action: 'MOVE', file: File): Promise<MoveActionResult>; // 函数实现 async function onAction(action: TAction, file: File) { switch (action) { case 'DELETE': { return { deleteRecursively: user.role === 'admin' }; } case 'RENAME': { const newName = window.prompt('Enter new name:', file.name); return { newName }; } case 'MOVE': { const newLocation = window.prompt('Enter new location:'); return { newLocation }; } default: throw new Error('Invalid action'); } }; const user = { role: "admin" }; const handleDelete = async (file: File) => { const result = await onAction("DELETE", file); // 修正fetch参数写法(原代码参数格式有误) await fetch(".../delete", { method: 'POST', body: JSON.stringify({ deleteRecursively: result.deleteRecursively }) }); };
方法2:给分支返回值添加类型断言
如果坚持使用泛型条件类型,可通过类型断言明确告诉TypeScript分支返回值对应ActionResult<T>:
type TAction = 'RENAME' | 'DELETE' | 'MOVE'; type DeleteActionResult = { deleteRecursively: boolean }; type RenameActionResult = { newName: string | null }; type MoveActionResult = { newLocation: string | null }; type ActionResult<T extends TAction> = T extends 'DELETE' ? DeleteActionResult : T extends 'RENAME' ? RenameActionResult : T extends 'MOVE' ? MoveActionResult : never; type TOnAction = <T extends TAction>(action: T, file: File) => Promise<ActionResult<T>>; const user = { role: "admin" }; const onAction: TOnAction = async <T extends TAction>(action: T, file: File) => { switch (action) { case 'DELETE': { return { deleteRecursively: user.role === 'admin' } as ActionResult<T>; } case 'RENAME': { const newName = window.prompt('Enter new name:', file.name); return { newName } as ActionResult<T>; } case 'MOVE': { const newLocation = window.prompt('Enter new location:'); return { newLocation } as ActionResult<T>; } default: throw new Error('Invalid action'); } }; const handleDelete = async (file: File) => { // 原代码空值合并无意义,直接取属性 const { deleteRecursively } = await onAction("DELETE", file); await fetch(".../delete", { method: 'POST', body: JSON.stringify({ deleteRecursively }) }); };
内容的提问来源于stack exchange,提问作者OtpExhaustv2
相关产品推荐
相关产品推荐

