获取 Typescript“参数”元组的“切片”

Rob*_*bin 24 javascript typescript

考虑Parameters实用程序类型,其底层类型是元组:https ://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype

我有一个功能SomeFunction。要将函数的参数用作类型,我编写Parameters<SomeFunction>.

现在假设我想使用函数的参数作为除第一个参数之外的类型。

显然对于数组我会使用类似的东西...args.slice(1)。但我不知道用于 Typescript 定义的切片实用程序。Omit仅适用于对象。

这个问题的答案提供了一个RemoveFirstFromTuple实用程序。但这有点复杂。是否有一种内置方法可以在类型定义中提取元组的一部分?

jca*_*alz 29

是的,您可以对函数类型使用条件类型推断,其方式与实用程序Parameters类型的 实现方式非常相似:

type ParametersExceptFirst<F> = 
   F extends (arg0: any, ...rest: infer R) => any ? R : never;
Run Code Online (Sandbox Code Playgroud)

相比于

// from lib.es5.d.ts
type Parameters<T extends (...args: any) => any> = 
  T extends (...args: infer P) => any ? P : never;
Run Code Online (Sandbox Code Playgroud)

并验证它是否有效:

declare function foo(x: string, y: number, z: boolean): Date;
type FooParamsExceptFirst = ParametersExceptFirst<typeof foo>;
// type FooParamsExceptFirst = [y: number, z: boolean]
declare function foo(x: string, y: number, z: boolean): Date;
Run Code Online (Sandbox Code Playgroud)

Playground 代码链接


更新:用数字文字对元组进行任意切片是可能的,但不漂亮并且有警告。首先让我们编写TupleSplit<T, N>一个接受元组T和数字文字类型,并在索引处N分割元组,返回两部分:第一部分是的第一个元素,第二部分是之后的所有内容。(如果大于长度,则第一块为全部,第二块为空):TNNTNTT

type TupleSplit<T, N extends number, O extends readonly any[] = readonly []> =
    O['length'] extends N ? [O, T] : T extends readonly [infer F, ...infer R] ?
    TupleSplit<readonly [...R], N, readonly [...O, F]> : [O, T]
Run Code Online (Sandbox Code Playgroud)

这是通过可变参数元组上的递归条件类型来实现的,因此比上面相对简单的实现计算量更大。如果您在长元组(长度超过 25 左右)上尝试此操作,您可能会看到递归错误。如果您在行为不当的类型(例如非固定长度元组或事物的联合)上尝试此操作,您可能会得到奇怪的结果。很脆弱; 小心一点。ParametersExceptFirst

让我们验证一下它是否有效:

type Test = TupleSplit<readonly ["a", "b", "c", "d", "e"], 3>
// type Test = [readonly ["a", "b", "c"], readonly ["d", "e"]]
Run Code Online (Sandbox Code Playgroud)

看起来不错。


现在我们可以使用TupleSplit<T, N>来实现TakeFirst<T, N>,仅返回 的第一个N元素T,并且SkipFirst<T, N>,它会跳过 的第一个N元素T

type TakeFirst<T extends readonly any[], N extends number> =
    TupleSplit<T, N>[0];

type SkipFirst<T extends readonly any[], N extends number> =
    TupleSplit<T, N>[1];
Run Code Online (Sandbox Code Playgroud)

最后通过获取结果的第一个元素并跳过结果的第一个元素来生成从开始位置到结束位置的TupleSlice<T, S, E>元组切片(记住,切片包括开始索引,不包括结束索引) :TSEETS

type TupleSlice<T extends readonly any[], S extends number, E extends number> =
    SkipFirst<TakeFirst<T, E>, S>
Run Code Online (Sandbox Code Playgroud)

为了证明这或多或少代表了数组的slice()作用,让我们编写一个函数并测试它:

function slice<T extends readonly any[], S extends number, E extends number>(
    arr: readonly [...T], start: S, end: E
) {
    return arr.slice(start, end) as readonly any[] as TupleSlice<T, S, E>;
}

const tuple = ["a", "b", "c", "d", "e"] as const
// const tuple: readonly ["a", "b", "c", "d", "e"]

const ret0 = slice(tuple, 2, 4);
// const ret0: readonly ["c", "d"]
console.log(ret0); // ["c", "d"]

const ret1 = slice(tuple, 0, 9);
// const ret1: readonly ["a", "b", "c", "d", "e"]
console.log(ret1); // ["a", "b", "c", "d", "e"];

const ret2 = slice(tuple, 5, 3);
// const ret2: readonly []
console.log(ret2); // [];
Run Code Online (Sandbox Code Playgroud)

这看起来不错;返回的数组slice()具有准确表示其值的类型。

当然,还有很多注意事项;如果将负数或非整数传递给slice()forSE,则TupleSlice<T, S, E>很可能与数组切片实际发生的情况不相符:负的“从末尾”行为可能是可以实现的,但它会更丑陋;非整数,甚至还number没有经过测试,但我预计递归警告和其他事情会在晚上发生。被警告!

Playground 代码链接