Skip to Content
Course content

188: Parameters<Type> and ConstructorParameters<Type>

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're tasked with building a specialized adapter for a piece of hardware. You don't have the original blueprints for the machine, but you have the machine itself. To make your adapter work, you need to know exactly what plugs into the machine—the number of pins, the voltage of each, and the order they go in. Instead of guessing or reading a 500-page manual, you use a "probe" that scans the machine's input port and gives you a precise list of what's required.

In TypeScript, Parameters<T> and ConstructorParameters<T> are those probes. They allow you to extract the type definitions of a function's arguments or a class constructor's arguments without having to manually define those types in a separate interface or type alias.

Peeking inside the function signature

Let's say you have a function that handles user registration. In a real app, this function might be defined in a library or a different module that you don't want to keep importing types from just to keep things DRY.

function registerUser(email: string, age: number, newsletter: boolean) {
  // complex registration logic here
  return { success: true };
}

// Now, imagine we want to create a "wrapper" or a "logger" 
// that takes the exact same arguments as registerUser.
type RegisterArgs = Parameters<typeof registerUser>;

Here is where the magic happens. RegisterArgs isn't just a generic list; it's a tuple. If you hover over it in your editor, you'll see it's exactly [email: string, age: number, newsletter: boolean]. I love this because if I ever change the registerUser function to add a username field, RegisterArgs updates automatically. I don't have to hunt through my codebase to update five different interfaces.

You can then use this tuple to ensure another function stays in sync:

function logRegistration(args: Parameters<typeof registerUser>) {
  console.log("Attempting registration with:", args);
}

// This works perfectly
logRegistration("dev@example.com", 30, true); 

Handling classes with ConstructorParameters

The logic for classes is almost identical, but since classes are instantiated using the new keyword, we use a specific utility called ConstructorParameters<T>. This targets the constructor specifically, rather than the methods inside the class.

Consider a DatabaseConnection class. It might require a host, a port, and a timeout.

class DatabaseConnection {
  constructor(public host: string, public port: number, public timeout: number = 5000) {
    console.log(`Connecting to ${host}:${port}...`);
  }
}

// We want to extract the types needed to instantiate this class
type DbConfig = ConstructorParameters<typeof DatabaseConnection>;
// DbConfig is now [host: string, port: number, timeout: number]

I've found this incredibly useful when building "Factory" patterns. If you have a function that creates instances of different classes, you can use this to ensure the factory is passing the correct arguments to the constructor without hard-coding the types for every single class you support.

Why we do this instead of using Interfaces

You might be wondering, "Why not just define a UserRegistrationRequest interface and use that in the function signature?"

In a perfect world, you would. But in the real world, you often deal with third-party libraries where the authors didn't export the argument types, or you're working in a massive codebase where changing one interface triggers a cascade of 50 type errors. Using these utility types lets you derive the truth directly from the implementation. It makes your code more resilient to change because the implementation is the single source of truth.




📋 Practical Task

Building a Generic API Request Interceptor

You are building a logging system for an API client. You have a function called fetchUserData that takes a userId (string), a token (string), and an optional includePrivateData (boolean).

Your task is to create a function called interceptRequest. This function must accept a "callback" (which will be fetchUserData) and the arguments required to call that function. To ensure type safety, you must use Parameters<T> to type the arguments of the interceptRequest function so that it dynamically matches whatever function is passed into it.

Requirements:

  • Define the fetchUserData function.
  • Implement interceptRequest using a generic type T extends (...args: any[]) => any.
  • Use Parameters<T> to ensure the args parameter of interceptRequest matches the signature of the provided callback.
  • The interceptRequest function should log "Intercepting request..." and then execute the callback with the provided arguments.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.