12

I would like to reference one function's parameter types in another function, but only use a subset of them.

//params of bar should be same as foo, except p1 should be a different type

function foo(p1: string, p2: number, p3: boolean){
 ...
}

//i'd like to do something like this, though it's obviously not valid syntax
function bar(p1: string[], ...rest: Parameters<typeof foo>.slice(1)){
}

Is there a way to do that? Obviously it's not hard to do manually with only 3 parameters, but in my actual code I have more parameters and I'd like to not repeat them.

bdwain
  • 1,665
  • 16
  • 35

4 Answers4

23

TypeScript 4.0 +

Option 1: Variadic tuple

type DropFirst<T extends unknown[]> = T extends [any, ...infer U] ? U : never

function bar(p1: string[], ...rest: DropFirst<Parameters<typeof foo>>) { }
// bar: (p1: string[], p2: number, p3: boolean) => void

Syntax for inferring all tuple elements except the first one now has become simpler. See Robby Cornelissen's answer for versions < 4.0.

Option 2: Labeled Tuple Elements

type CommonParams = [p2: number, p3: boolean];

function foo2(p1: string, ...rest: CommonParams){} 
// foo2: (p1: string, p2: number, p3: boolean) => void
function bar2(p1: string[], ...rest: CommonParams) { }
// bar2: (p1: string[], p2: number, p3: boolean) => void

Named tuples can be used to preserve function parameter names, so there is no lossy conversion.

Playground

ford04
  • 66,267
  • 20
  • 199
  • 171
  • I think option 1 would need to be `type DropFirst = T extends [any, ...infer U][] ? U[] : never` (to avoid the error about rest params needing to be an array type) but that makes every parameter in U the union of all parameter types, rather than restricting each param to the right type – bdwain Jul 22 '20 at 16:24
  • Not sure, what you are meaning, can you give an example? If you wanted to assert, that `DropFirst` always spits out an array type, you could define it like [this](https://www.typescriptlang.org/play/?ts=4.0.0-beta&ssl=1&ssc=1&pln=1&pc=93#code/C4TwDgpgBAIgTgezAMQJZwM7ADwBUoQAewEAdgCYZQDaArqQNakIDupANFAHQ-1OulqAXSEA+KAF4o+IiQpVqAQ1IhOPLqlIAzCHCgBVIVAD8BqAC4opCADddAbgBQoSLEQp0WAIyS3SNJg4wqLO4NDw-p7AAEy+ER6B2NReYqGu8QFYAMxx7plBXuzRqUA): `type DropFirst = T extends [any, ...infer U] ? U : never;` – ford04 Jul 22 '20 at 18:36
  • 1
    oh sorry my mistake. I was accidentally on typescript 3.9. Switching to 4.0 does indeed make it work like you originally described. Thanks! – bdwain Jul 22 '20 at 22:10
  • when trying to use `rest` I get `Type 'never' must have a '[Symbol.iterator]()' method that returns an iterator.ts(2488)` – John Snow Jun 14 '21 at 18:18
  • @JohnSnow sounds like the type resolution of the passed in value went wrong – ford04 Jun 17 '21 at 04:39
4

You can create a type tuple using the Parameters utility type, and then create a new type from that, omitting the first type:

type Rest<T extends any[]> = 
  ((...p: T) => void) extends ((p1: infer P1, ...rest: infer R) => void) ? R : never;

function foo(p1: string, p2: number, p3: boolean) {
}

function bar(p1: string[], ...rest: Rest<Parameters<typeof foo>>) {
}


bar([''], 0, true); // ok
bar('', 0, true); // error
bar([''], true, 0); // error

See also this answer.

Robby Cornelissen
  • 91,784
  • 22
  • 134
  • 156
1

You would use Destructured Object Parameters. By passing an object into a function, you can select to only use a subset of the fields of that parameter object.

You can define the parameter interface type separately (as with foo below), or you can define it inline (like in bar below).

You can then pass the same object (containing your parameters as fields) into both, and the functions would only select what they need.

type FooParameters = { p1: string, p2: number, p3: boolean }
function foo({ p1, p2, p3 }: FooParameters) {
  console.log("From foo:", p1, p2, p3)
}

//i'd like to do something like this, though it's obviously not valid syntax
function bar({ p1 }: { p1: string }) {
  console.log("From bar:", p1)
}

const parameters = { p1: "First Parameter", p2: 2, p3: false }

foo(parameters)
bar(parameters)
Luke Storry
  • 6,032
  • 1
  • 9
  • 22
  • yea an object would be simpler, though unfortunately I'm typing an existing method and don't want to change the signature – bdwain Jul 22 '20 at 05:33
1

The easiest method would be to have a shared rest parameter type:

type SharedParams = [number, boolean];

//params of bar should be same as foo, except p1 should be a different type

function foo(p1: string, [p2, p3]: SharedParams){
    ...
}

//i'd like to do something like this, though it's obviously not valid syntax
function bar(p1: string[], ...rest: SharedParams){
    ...
}

Playground

Elias Schablowski
  • 2,619
  • 9
  • 19
  • Ah yea extracting it does work well, though one limitation is that the variables lose their names in the type definitions if you spread the array. `foo(p1: string, __1_0: number, __1_1: boolean): void` is what I see when I do that – bdwain Jul 22 '20 at 05:32