infer infers the matter of "parameters/return values"
1. Let me give you a "human version of conclusion" first.
inferhas only one function: When theextendsjudgment 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)
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
type Fn = (a: number, b: string) => boolean;
type R = MyReturnType<Fn>;What TS is doing in his head:
(a: number, b: string) => boolean
extends (...args: any[]) => infer R👉 Match successful 👉 Infer:
R = boolean;✅ Final result:
type R = boolean;3. infer inference 【 parameters 】 (slightly more difficult)
1️⃣ Standard writing method (Parameters principle)
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
type Fn = (id: number, name: string) => void;
type P = MyParameters<Fn>;Inference process:
Fn extends (...args: infer P) => any👉 Match successful 👉 TS automatic inference:
P = [number, string];✅ Result:
type P = [number, string];4. Why is the parameter a "tuple"?
Because the function parameters already have:
- order
- number
- Type of each item
(a: number, b: string)
↓
[number, string]📌So Parameters must return a tuple
5. Look at "parameters + return value" together (it will be clearer)
type Fn = (x: number, y: string) => boolean;
type Args = Parameters<Fn>; // [number, string]
type Res = ReturnType<Fn>; // booleanYou 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
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 want | How to write |
|---|---|
| Return value | (...args) => infer R |
| Parameters | (...args: infer P) => any |
Example 1
What you don’t understand is this sentence 👇
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 👇
T extends (...args: any[]) => any ? X : YThis sentence means:
If T is a function type
- is → return
X- No → return
Y
For example:
type A = string extends Function ? 1 : 2; // 2
type B = (() => void) extends Function ? 1 : 2; // 1So far, Not mysterious at all , right?
Step 2: What is (...args: any[]) => any?
This is a function type:
(...args: any[]) => any;means:
A function with any parameters and any return value
So:
T extends (...args: any[]) => anyIt 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👇
(...args: infer P) => any;This sentence means:
If T is a function, grab its "parameter list", Store it in a variable P
⚠️ NOTE:
PNot from you- It is TS automatically inferred for you
Use a very specific example
type Fn = (a: number, b: string) => void;We substitute:
Fn extends (...args: infer P) => any ? P : neverWhat is TS doing?
👉 It’s asking:
Can this
Fnmatch this function structure?
(...args: ???) => any;The answer is: Yes
So TS automatically infers:
P = [number, string];Step 4: Count the entire type together (complete process)
type Params<T> = T extends (...args: infer P) => any ? P : never;Case 1: T is a function
type A = Params<(x: number, y: string) => void>;Calculation process:
(x: number, y: string) => void
extends (...args: infer P) => any👉 Match successful 👉 P = [number, string]
Result:
type A = [number, string];Case 2: T is not a function
type B = Params<string>;Calculation:
string extends (...args: infer P) => any ❌👉 go : never
Result:
type B = never;One sentence version (please remember this)
infer = "Save a certain part of the type under a variable name"
Here it is:
infer PThat is:
Save the parameter list of the function into P
Translate an entire line of code in one sentence
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:
Parameters<T>;
ReturnType<T>;
ConstructorParameters<T>;For example:
type P = Parameters<(a: number, b: string) => void>;
// [number, string]Example 2
What you don’t understand is this sentence 👇
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)
T extends XXX ? A : BThis 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:
Promise<something>;You're familiar with JS:
Promise<number>;
Promise<string>;Now this sentence:
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.
type T = Promise<number>;Substitute:
Promise<number> extends Promise<infer R>TS will say:
👉 Well, the structure is the same 👉 Then I deduce:
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:
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
type A = Awaited<Promise<number>>;Calculation process:
first time
T = Promise<number>;✔ is a Promise 👉 R = number 👉 result = Awaited<number>
second time
T = number;❌ Not a Promise 👉 Go : T
结果 = number;✅ Final result: number
🌰 Example 2: Promise within Promise (that’s why it exists)
type B = Awaited<Promise<Promise<string>>>;first time
T = Promise<Promise<string>>
R = Promise<string>
→ Awaited<Promise<string>>second time
T = Promise<string>
R = string
→ Awaited<string>third time
T = string
❌ 不是 Promise
→ string✅ Final result: string
Now translate the entire code in one sentence in vernacular
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:
const res = await await await promise;In 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