typescript - 我如何告诉 typescript 元组中的最后一个参数始终是一个函数?

标签 typescript

我创建了一个函数,它使用扩展运算符来接受具有各种参数数量的重载。不幸的是, typescript 说该函数的参数之一不可调用。下面是我所指内容的示例。

type Arguments=[{():void}]|[string,{():void}]|[string,number,{():void}];

function overload(...args:Arguments){
  //Do stuff depending on number of arguments
  if(args.length===2)console.log(args[0]);
  if(args.length===3)console.log(args[0],args[1]);

  args[args.length-1](); //TS error here
}
//An example of how I'd like to call it
overload(()=>console.log(`I'm always the last..`));
overload('example',()=>console.log(`..and I'm a function..`));
overload('example',1,()=>console.log(`..that can be called`));

最后一个参数始终是一个函数,因为元组[{():void}]|[string,{():void}]|[string,number,{():void}] 其中每个都以 {():void} 结尾。因此可以像 args[args.length-1](); 那样调用它,但我得到:

This expression is not callable. Not all constituents of type 'string | number | (() => void)' are callable. Type 'string' has no call signatures.ts(2349)

我的问题是:我怎样才能告诉 typescript ,无论如何 - 上面显示的数组的最后一项是我可以调用的函数?这是做这样的事情的最好方法吗?关于如何以更好的方法实现与上述相同的结果有什么建议吗?

我已经尝试过:

//NOPE
function overload(...args:Arguments):void{
  /* ... */
  if(typeof args[args.length-1]==='function'){
    args[args.length-1]();
  }
}

//NOPE
function overload(...args:[{():void}]):void;
function overload(...args:[string,{():void}]):void;
function overload(...args:[string,number,{():void}]):void;
function overload(...args:[{():void}]|[string,{():void}]|[string,number,{():void}]):void{
  /* ... */
  args[args.length-1]();
}

我知道我可以做这样的事情来克服这个问题:

function overload(...args:Arguments){
  /* ... */
  const last=args[args.length-1] as {():void};
  last();
}

但我只调用数组的最后一项一次。我不想仅仅为了完成这项工作而创建一个特殊的变量,这也是我编写类型的原因 - 消除错误而不是创建新的“假”错误,对吧?这是 args.length-1 吗? TS 无法处理的某种未知值,但如果是这样,为什么 TS 会假设它是 'string | 之一?数量 | (() => void)' 类型?

代码按照预期工作(用纯 JavaScript 编写)。原始代码比这更高级,因此,我简化了它,在我看来它并没有改变它的主要含义。如果重要的话,我正在使用 typescript 3.9.5。

最佳答案

您希望编译器识别 args[args.length-1]将返回元组的最后一个元素,但当前不支持。

编译器理解 args[0]返回第一个元素,并使用 numeric literal type 索引到元组中也可以工作,但即使编译器看到 args.length1由于是数字文字类型,减法运算仅产生 number 。您目前无法使用数字文字类型进行数学计算;请参阅microsoft/TypeScript#26382寻求改变这种状况的公开建议。即使这样,您也可能会遇到像 Arguments 这样的联合类型的问题。 ,因为编译器需要认识到 args.length-1 的类型与 args 的类型相关 ,编译器也不擅长。请参阅microsoft/TypeScript#30581了解更多信息。


更有希望的是拥有 last() 的想法函数接受 T 类型的元组并返回 Last<T> 类型的值哪里Last提取元组最后一个元素的类型。这并不容易,因为 TypeScript 的元组类型操作支持目前以 using them in rest parameters 为中心。 ,因此您需要跳过涉及函数类型的麻烦。有一些 Unresolved 问题需要更一般的操作,例如 microsoft/TypeScript#26223 ,但还没有任何东西成为该语言的一部分。

此外,最好避免循环条件类型(请参阅 microsoft/TypeScript#26980 ),以便以下操作不会使用它们。

首先我会写Tail<T> ,它需要一个元组 T并返回一个与 T 相同的新元组删除了第一个元素。如果T[1,2,3] ,然后Tail<T>[2,3] :

type Tail<T extends readonly any[]> =
    ((...t: T) => void) extends ((h: any, ...r: infer R) => void) ? R : never;

现在Last<T>可以用 Tail<T> 来定义。做法是: if T是一个数组或 open-ended tuple ,然后只需索引 T有一些非常大的数字索引。否则,找到 T 的一键这不是 Tail<T> 的键,并索引到 T接着就,随即。您可以验证,例如 Last<[1, 2, 3]>3

type Last<T extends readonly any[]> = number extends T['length'] ? T[1e100] : {
    [K in keyof T]: K extends keyof Tail<T> ? never : T[K] }[number];

然后我们将声明 last()像这样:

function last<T extends readonly any[]>(t: T): Last<T>;
function last(t: any[]) {
    return t[t.length - 1];
}

请注意,编译器无法验证 t[t.length-1]类型为Last<T> ,所以我们需要断言(这里我使用单个 overload ,其实现签名比调用签名更宽松)。


配备last() ,我们可以试试你的overload() :

function overload(...args: Arguments) {
    if (args.length === 2) console.log(args[0]);
    if (args.length === 3) console.log(args[0], args[1]);
    last(args)(); // okay
}

这有效!如果我检查 last(args) 的类型,您将看到您所期望的内容:

const f = last(args);
// const f: () => void

如果您更改 Arguments 中任何元组的最后一个元素union 使其不是零参数函数类型,编译器会警告您:

function badOverload(...args: Arguments | [string, number, boolean, string]) {
    const f = last(args);
    // const f: string | () => void
    last(args)(); // error! string is not callable
}

好的,希望对你有用。祝你好运!

Playground link to code

关于typescript - 我如何告诉 typescript 元组中的最后一个参数始终是一个函数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62249401/

相关文章:

Angular 惰性路由忽略父路径

typescript - 推断类参数时将忽略通用约束

visual-studio-2013 - 成功构建, typescript 输入文件中有许多错误

node.js - 解析错误 : Cannot read file '.../tsconfig.json' . eslint

typescript - MongoMemoryServer ;未处理的错误

typescript - 如何在 Angular 2 模板中将 .bind() 值预先添加到组件的方法中

javascript - 函数 ngOnChanges() 在 Angular 5 中不起作用

javascript - Angular:多选下拉 ID

javascript - [Vue警告] : Failed to resolve component when tried to create global component

typescript - ES6 模块、VueJS 和 TypeScript