其余元组语法允许您以常规调用签名不具备的方式抽象函数参数列表。如果您只打算使用一次抽象,那么它很少有用。想象一下,如果你被问到“JavaScript 中的函数有什么意义?” 并给出这个例子:
// why do this:
const val = ((x: number) => x * x)(5);
// when the following is simpler:
const val = 5 * 5;
当然,孤立地讲,没有理由创建一个类似 的函数x => x * x,调用它一次,然后将其丢弃。直接乘法显然更胜一筹。不过,这里的问题不在于功能,而在于示例。
因此,让我们看看休息元组以及一旦您被允许对参数列表进行抽象时它们可以使用的一些不同的东西:
在不同的函数类型中重用参数列表
假设您有一组函数,它们都采用相同的参数列表,但返回类型不同:
declare function foo(x: string, y: number, z: boolean): string;
declare function bar(x: string, y: number, z: boolean): number;
declare function baz(x: string, y: number, z: boolean): boolean;
// ... and maybe more
假设这不仅仅是巧合,而且函数以某种方式相关,您可能希望在某个时候更改参数列表,并且更愿意为每个函数执行一次而不是一次。使用其余元组,您可以这样做:
type Params = [x: string, y: number, z: boolean];
declare function foo(...args: Params): string;
declare function bar(...args: Params): number;
declare function baz(...args: Params): boolean;
这是等效的。如果我想,比如说,添加第四个参数,我可以在Params.
参数列表的操作
假设我有相关的函数,它们有一些参数:
declare function qux(x: string, y: number, z: boolean): number
declare function quux(w: Date, x: string, y: number, z: boolean): string;
可能是因为quux()'s implementation 调用qux()。您可以重写quux()的签名以改用剩余元组:
declare function quux(w: Date, ...rest: Parameters<typeof qux>): string;
我正在使用Parameters实用程序类型从qux函数中提取元组类型的参数。如果qux()' 的参数发生变化,则quux()' 的参数将自动更改。
参数列表的联合而不是重载
想象一下,我们有以下重载 frob()函数:
function frob(k: "number", val: number): void;
function frob(k: "string", val: string): void;
function frob(k: "number" | "string", val: number | string) {
if (k === "number") {
console.log(val.toFixed(1)); // error, oops
} else {
console.log(val.toUpperCase()); // error, oops
}
}
作为呼叫者,您可以打电话frob("number", 123)或打电话,frob("string", "hello")但不能打电话frob("number", "hello")。但是,的实现frob()并不是那么好,因为编译器真的不明白k === "number"只有当val是 a时才会发生这种情况number。实现中的参数k和val只是不相关的联合。所以你会得到一些错误。当然,这很容易解决类型断言。
但请考虑以下版本的表现frob()力:
function frob(...args: [k: "number", val: number] | [k: "string", val: string]) {
if (args[0] === "number") {
console.log(args[1].toFixed(1)); // okay
} else {
console.log(args[1].toUpperCase()); // okay
}
}
从调用者的角度来看,这与以前几乎相同。剩余元组的联合最终表现得像多个调用签名。但实现方式不同。编译器现在知道args其余参数是一个可区分的 union;如果第一个元素是"number",那么第二个元素肯定是 a number,编译器知道。所以它类型检查。
与参数列表相关的通用函数
考虑以下函数withConsoleMessage()。它接受一个函数作为输入,然后返回另一个函数,该函数string首先接受一个消息参数,然后是输入函数的所有参数。当您调用该函数时,它会记录消息并调用原始函数:
function withConsoleMessage<A extends any[], R>(
cb: (...a: A) => R
): (msg: string, ...a: A) => R {
return (msg, ...a) => (console.log(msg), cb(...a));
}
const atan2WithMessage = withConsoleMessage(Math.atan2);
const ret = atan2WithMessage("hello!", 4, 4); // "hello!"
console.log(ret * 4); // 3.141592653589793
看那个; 编译器知道atan2WithMessage()需要 astring然后是两个numbers。如果没有元组类型的剩余元素,就很难开始给出withConsoleMessage()类型签名。我想你可以给它一堆重载,这样它就可以接受不同参数的回调,直到某个任意限制:
function withConsoleMessage<R>(cb: () => R): (msg: string) => R;
function withConsoleMessage<R, A>(cb: (a: A) => R): (msg: string, a: A) => R;
function withConsoleMessage<R, A, B>(cb: (a: A, b: B) => R): (msg: string, a: A, b: B) => R;
function withConsoleMessage<R, A, B, C>(cb: (a: A, b: B, c: C) => R): (msg: string, a: A, b: B, c: C) => R;
function withConsoleMessage(cb: Function) {
return (msg: string, ...a: any[]) => (console.log(msg), cb(...a));
}
这有点工作,在 TypeScript 3.0 之前,如果你想近似这种行为,这是你必须做的。与通用的休息元组相比,它又丑又脆。
我想我现在就到此为止;毕竟,我不知道我还要花多少时间来回答一个已经接受了不同答案的问题!可以说像 rest tuples 这样的抽象开辟了许多可能性。
Playground 代码链接