You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 01:12:19