我想写这样一个工厂函数(它返回一个函数),它接受1-2个泛型类型(RP,P是可选的):

  • 如果它得到1个泛型类型,则返回一个形状为() => R的函数.
  • 如果值为2,则返回一个形状为(p: P) => R的函数.
type MapLike = Record<string, any>;

function createSomeFn<R extends MapLike>(
  key: string,
): () => R;

function createSomeFn<R extends MapLike, P extends MapLike>(
  key: string,
): (param: P) => R;

我该怎么做呢?我觉得这是一个函数重载的问题.

我try 了以下方法,当它接受2种类型时有效,但只接受1种类型失败.

type MapLike = Record<string, any>;

function createSomeFn<R extends MapLike, P extends MapLike = never>(
  key: string,
) {
  return function (p: P): R {
    return doSomeThing(key, p);
  }
};
  • 对于两种类型,它都是有效的

    type Pe = { foo: string };
    type Re = { bar: string };
    const myFn = createSomeFn<Re, Pe>('myFn');
    
    // ✅ It alerts me that there should be a `p` argument
    // myFn();
    // ^^^^^^ error TS2554: Expected 1 arguments, but got 0
    
    myFn({ foo: 'bar' });
    
  • 如果类型为1,则失败

    type Re = { bar: string };
    const myFn = createSomeFn<Re>('myFn');
    
    // ❌ It should let me go, but raise error
    // myFn();
    // ^^^^^^ error TS2554: Expected 1 arguments, but got 0
    
    // myFn({});
    // ^^^^^^^^ error TS2345: Argument of type '{}' is not assignable to parameter of type 'never'
    

推荐答案

如果您希望能够以不同的方式调用单个函数,则可以将其设置为overload,也可以使用conditional types将其设置为generic以表示任意类型关系.


对于您的示例,重载解决方案相对简单:只需为调用它的每种所需方式添加一个调用签名:

// call signatures
function createSomeFn<R extends MapLike>(key: string): () => R;
function createSomeFn<R extends MapLike, P extends MapLike>(
  key: string): (param: P) => R;

然后使用签名实现它,该签名将允许任何此类调用(至少):

function createSomeFn<R extends MapLike, P extends MapLike = never>(
  key: string) {
  return function (p: P): R {
    return null! // impl later
  }
};

如果要改用泛型条件类型,则需要确定作为输入类型的函数的返回类型.您希望将Pnever进行比较,如果匹配则返回() => R,如果不匹配则返回(p: P) => R.有一种方法可以做到这一点:

function createSomeFn<R extends MapLike, P extends MapLike = never>(
  key: string,
) {
  return function (...[p]: ([P] extends [never] ? [] : [p: P])): R {
    return null! // impl later
  }
};

支票的格式是[P] extends [never] ? ⋯ : ⋯,而不是P extends never ? ⋯ : ⋯,因为我们不想要distributive conditional type(见microsoft/TypeScript#23182).在这里,我根据P来 Select 使用哪一种rest parameter tuple type.如果Pnever,则参数列表应该为空([]),否则它应该是类型P的一元组([p: P]).


这两种解决方案的运行方式都符合您的要求:

type Pe = { foo: string };
type Re = { bar: string };
const myFn = createSomeFn<Re, Pe>('myFn');
myFn(); // error
myFn({ foo: 'bar' }); // okay
const myFn2 = createSomeFn<Re>('myFn');
myFn2(); // okay
myFn2({}); // error

Playground link to code

Typescript相关问答推荐

TypeScript泛型参数抛出错误—?&#"' 39;预期"

如何判断输入是否是TypeScript中的品牌类型?

如何提取密钥及其对应的属性类型,以供在新类型中使用?

如何在Typescript 中输入两个相互关联的变量?

如何按不能保证的功能过滤按键?

如何在TypeScrip中正确键入元素和事件目标?

错误TS2403:后续变量声明必须具有相同的类型.变量';CRYPTO';的类型必须是';CRYPATO';,但这里的类型是';CRYPATO';

函数重载中的不正确TS建议

API文件夹内的工作员getAuth()帮助器返回:{UserID:空}

在TypeScript中扩展重载方法时出现问题

为什么上下文类型在`fn|curred(Fn)`的联合中不起作用?

TS2739将接口类型变量赋值给具体变量

在单独的组件中定义React Router路由

编剧- Select 下拉框

如何在Deno中获取Base64图像的宽/高?

如何创建一个将嵌套类属性的点表示法生成为字符串文字的类型?

仅当类型为联合类型时映射属性类型

如何将 MUI 主题对象的自定义属性与情感样式组件中的自定义props 一起使用?

剧作家测试未加载本地网址

如何在故事书的Typescript 中编写decorator ?