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]();
,但我得到:
此表达式不可调用。并非所有类型为 'string | 号码 | (() => void)' 是可调用的。类型“字符串”没有调用签名.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 假定它是“字符串 | 号码 | (() => void)' 类型?
代码按预期工作(用纯 javascript 编写)。原始代码比这个高级一点,因此,我对其进行了简化,在我看来它并没有改变它的主要含义。如果重要的话,我正在使用 typescript 3.9.5。
解决方案
您希望编译器识别args[args.length-1]
将返回元组的最后一个元素,但目前不支持。
编译器知道args[0]
返回第一个元素,并且索引到具有数字文字类型的元组也将起作用,但即使编译器将args.length
and1
视为数字文字类型,减法运算只会产生number
. 您目前无法使用数字文字类型进行数学运算;请参阅microsoft/TypeScript#26382以获取更改它的公开建议。即使这样,您也可能会遇到联合类型之类的问题Arguments
,因为编译器需要意识到 of 的类型与 ofargs.length-1
的类型相关args
,而编译器也不擅长这一点。有关详细信息,请参阅microsoft/TypeScript#30581。
更有希望的是有一个last()
函数的想法,它接受一个类型的元组T
并返回一个类型的值,Last<T>
其中Last
提取元组的最后一个元素的类型。这并不容易,因为 TypeScript 的元组类型操作支持目前围绕在 rest parameters 中使用它们,所以你需要跳过涉及函数类型的箍。有一些开放的问题要求进行更一般的操作,例如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>
。方法是:如果T
是数组或开放式元组,则只需T
使用一些非常大的数字索引进行索引。否则,找到其中一个键T
不是 的键Tail<T>
,并以此为索引T
。例如,您可以验证Last<[1, 2, 3]>
is 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]
是 type Last<T>
,因此我们需要断言(这里我使用的是单个重载,其实现签名比调用签名更宽松)。
有了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
联合中任何元组的最后一个元素,使其不是零参数函数类型,编译器会警告您:
function badOverload(...args: Arguments | [string, number, boolean, string]) {
const f = last(args);
// const f: string | () => void
last(args)(); // error! string is not callable
}
好的,希望对你有用。祝你好运!
推荐阅读
- drone.io - drone.io:containerd:写 /proc/14/oom_score_adj:权限被拒绝
- python - Python 请求 POST 未发布
- kotlin - 如何将外部可调用成员函数添加到匿名对象?
- vbscript - QTP/UFT - 在 SendKeys 函数中包含选择所有文本
- angular - 在 Angular 5 中使用表单创建带有子组件的对象
- modbus - Modbus服务器节点寄存器映射策略
- javascript - 如何在 Laravel 中路由到 JSON 文件?
- r - R - 添加新分组列时聚合函数不同的结果
- node.js - Mongoose findoneandupdate 返回更新的文档但未在数据库中更新
- excel - 突出显示基于 pf 列标准的行 VBA