Skip to content
Created: December 7, 2020

infer infers the matter of "parameters/return values" ​

1. Let me give you a "human version of conclusion" first. ​

infer has only one function: When the extends judgment is successful, "catch" a certain part of the type and give it a name

In functions, what it most often captures is:

  • Parameter list
  • return value

2. infer inference 【 return value 】 (easiest) ​

1️⃣ Standard writing method (ReturnType principle) ​

ts
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

Translated word-for-word into Chinese ​

If T is a function Just take out its "return value type" and call it R Otherwise return never

2️⃣ Calculate it with specific examples ​

ts
type Fn = (a: number, b: string) => boolean;

type R = MyReturnType<Fn>;

What TS is doing in his head: ​

ts
(a: number, b: string) => boolean
extends (...args: any[]) => infer R

👉 Match successful 👉 Infer:

ts
R = boolean;

✅ Final result:

ts
type R = boolean;

3. infer inference 【 parameters 】 (slightly more difficult) ​

1️⃣ Standard writing method (Parameters principle) ​

ts
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;

Human version ​

If T is a function Just take out its "parameter list" and turn it into a tuple Otherwise return never

2️⃣ Specific examples ​

ts
type Fn = (id: number, name: string) => void;

type P = MyParameters<Fn>;

Inference process:

ts
Fn extends (...args: infer P) => any

👉 Match successful 👉 TS automatic inference:

ts
P = [number, string];

✅ Result:

ts
type P = [number, string];

4. Why is the parameter a "tuple"? ​

Because the function parameters already have:

  • order
  • number
  • Type of each item
ts
(a: number, b: string)
↓
[number, string]

📌So Parameters must return a tuple

5. Look at "parameters + return value" together (it will be clearer) ​

ts
type Fn = (x: number, y: string) => boolean;

type Args = Parameters<Fn>; // [number, string]
type Res = ReturnType<Fn>; // boolean

You can now Now you fully understand the principles of these two built-in types. 。

6. A real front-end example (you will get it right away) ​

1️⃣ Wrap a layer of functions without losing the type ​

ts
function withLog<T extends (...args: any[]) => any>(fn: T) {
  return (...args: Parameters<T>): ReturnType<T> => {
    console.log(args);
    return fn(...args);
  };
}
  • 📌 The point is not that you can write it
  • 📌 Now you can understand what each part is doing

7. The three iron rules of infer (just memorize them) ​

1️⃣ infer can only be used in extends 2️⃣ infer only takes effect when the conditions are met 3️⃣ infer is automatically inferred by TS, not passed by you

8. Minimalist memory method 🧠 ​

What you wantHow to write
Return value(...args) => infer R
Parameters(...args: infer P) => any

Example 1 ​

What you don’t understand is this sentence 👇 ​

ts
type Params<T> = T extends (...args: infer P) => any ? P : never;

I will break it into 4 steps, and each step will be "understood before proceeding".

Step 1: First look at the “parts you already know” ​

Let’s remove the scariest infer P first and look at one you are familiar with 👇

ts
T extends (...args: any[]) => any ? X : Y

This sentence means:

If T is a function type

  • is → return X
  • No → return Y

For example:

ts
type A = string extends Function ? 1 : 2; // 2
type B = (() => void) extends Function ? 1 : 2; // 1

So far, Not mysterious at all , right?

Step 2: What is (...args: any[]) => any? ​

This is a function type:

ts
(...args: any[]) => any;

means:

A function with any parameters and any return value

So:

ts
T extends (...args: any[]) => any

It is equivalent to asking a person:

Is T a function?

Step 3: What does infer P do (key point) ​

Now let’s look at the core👇

ts
(...args: infer P) => any;

This sentence means: ​

If T is a function, grab its "parameter list", Store it in a variable P

⚠️ NOTE:

  • P Not from you
  • It is TS automatically inferred for you

Use a very specific example ​

ts
type Fn = (a: number, b: string) => void;

We substitute:

ts
Fn extends (...args: infer P) => any ? P : never

What is TS doing?

👉 It’s asking:

Can this Fn match this function structure?

ts
(...args: ???) => any;

The answer is: Yes

So TS automatically infers:

ts
P = [number, string];

Step 4: Count the entire type together (complete process) ​

ts
type Params<T> = T extends (...args: infer P) => any ? P : never;

Case 1: T is a function ​

ts
type A = Params<(x: number, y: string) => void>;

Calculation process:

ts
(x: number, y: string) => void
extends (...args: infer P) => any

👉 Match successful 👉 P = [number, string]

Result:

ts
type A = [number, string];

Case 2: T is not a function ​

ts
type B = Params<string>;

Calculation:

ts
string extends (...args: infer P) => any ❌

👉 go : never

Result:

ts
type B = never;

One sentence version (please remember this) ​

infer = "Save a certain part of the type under a variable name"

Here it is:

ts
infer P

That is:

Save the parameter list of the function into P

Translate an entire line of code in one sentence ​

ts
type Params<T> = T extends (...args: infer P) => any ? P : never;

⬇️

If T is a function, return its parameter list; If it is not a function, return never

Why is this thing so important? ​

Because of the following You will use it every day from now on , the essence is it:

ts
Parameters<T>;
ReturnType<T>;
ConstructorParameters<T>;

For example:

ts
type P = Parameters<(a: number, b: string) => void>;
// [number, string]

Example 2 ​

What you don’t understand is this sentence 👇 ​

ts
type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T;

I will break it into 5 very small steps, each step is very "silly", but you can definitely understand it.

Step 1: Look at the “outermost structure” first (you’ve actually seen it) ​

ts
T extends XXX ? A : B

This is the typed version of the if statement:

If T matches XXX → Use A Otherwise → Use B

So far nothing new.

Step 2: What does Promise<infer R> do? ​

Forget about infer for now, just look at:

ts
Promise<something>;

You're familiar with JS:

ts
Promise<number>;
Promise<string>;

Now this sentence:

ts
T extends Promise<infer R>

Translated into human words:

T Is it a Promise? If so, take out the contents of Promise

Step 3: infer R = "auto-unpacking" ​

This is key, but we say a human word is enough:

infer R = "If the match is successful, take out the type inside and call it R"

Give me an example that you are 100% familiar with. ​

ts
type T = Promise<number>;

Substitute:

ts
Promise<number> extends Promise<infer R>

TS will say:

👉 Well, the structure is the same 👉 Then I deduce:

ts
R = number;

Step 4: Now look at ? Awaited<R> : T ​

We already know:

  • if T is a Promise → R is the value inside
  • If it is not a Promise → return T directly

but here does not return R , instead:

ts
Awaited<R>;

⚠️ Here comes the key point:

It throws R back into Awaited again

This step is called: recursion

Step 5: Walk through it all (most important) ​

🌰 Example 1: Normal Promise ​

ts
type A = Awaited<Promise<number>>;

Calculation process:

first time ​

ts
T = Promise<number>;

✔ is a Promise 👉 R = number 👉 result = Awaited<number>

second time ​

ts
T = number;

❌ Not a Promise 👉 Go : T

ts
结果 = number;

✅ Final result: number

🌰 Example 2: Promise within Promise (that’s why it exists) ​

ts
type B = Awaited<Promise<Promise<string>>>;

first time ​

ts
T = Promise<Promise<string>>
R = Promise<string>
→ Awaited<Promise<string>>

second time ​

ts
T = Promise<string>
R = string
→ Awaited<string>

third time ​

ts
T = string
❌ 不是 Promise
→ string

✅ Final result: string

Now translate the entire code in one sentence in vernacular ​

ts
type Awaited<T> = T extends Promise<infer R> ? Awaited<R> : T;

⬇️

If T is a Promise, unwrap it If there is still Promise inside after disassembly, continue to disassemble it. Until it is dismantled until it is no longer a Promise Then return that value

The relationship with await (you will get through it soon) ​

In JS:

ts
const res = await await await promise;

In TS:

ts
type Res = Awaited<typeof promise>;

👉 Exactly the same thought

You just need to remember these 3 sentences now (really) ​

  • 1️⃣ infer = Help me take out the types inside
  • 2️⃣ Awaited<R> = Continue to dismantle until it cannot be dismantled
  • 3️⃣ Entire type = Simulate the await behavior of JS