There are several TypeScript types. Try to guess what res is:

The first:

Incoming type parameters of joint type 1 | ‘a’, asked what’s res

type Test<T> = T extends number ? 1 : 2;

type res = Test<1 | 'a'>;
Copy the code

The second:

The type argument passed in is Boolean, asking what res is

type Test<T> = T extends true ? 1 : 2;

type res = Test<boolean>;
Copy the code

The third:

The type argument passed in is any, asking what res is

type Test<T> = T extends true ? 1 : 2;

type res = Test<any>;
Copy the code

Fourth:

The type parameter passed is never, asking what res is

type Test<T> = T extends true ? 1 : 2;

type res = Test<never>;
Copy the code

I’m going to post the correct answer, and let’s see how many of you got it right.

The answers posted

The first type res is 1 | 2

Look at the second type, the res is also 1 | 2

The following is the third type, res is also 1 | 2

And finally, the fourth type, res is never

It doesn’t matter how many you get right, the key is to know the reason for it, and I’ll explain:

Reasons why

The first type res is 1 | 2

Some of you might say, well, this is a union type, not a number, so why is that?

When a condition type has type parameters on the left, it has the property of distributive, that is, each type of the union type is passed in separately and evaluated, and the results of each type are merged into the union type. This is called distributed conditional type.

Here T extends the number on the left is a type parameter T, the incoming is joint type 1 | ‘a’, so will the 1 to evaluate, the 2 incoming evaluated, finally the result merged into joint types, namely 1 | 2.

Look at the second type, the res is also 1 | 2

Why is 1 | 2 here, just say the distributed condition type is for joint type?

Yes, Boolean is joint type, so the true and false incoming evaluated respectively, the result merged into joint type, so it is 1 | 2.

The following is the third type, res is also 1 | 2

Student: Oh, I see. Any is also the union type.

TrueType and falseType are returned directly as union types if the left side is ANY.

And finally, the fourth type, res is never

“Never” means “1” and “2”.

This is really a special treatment for TS, which returns never when the condition type is left with never.

How do I know if you’re telling the truth, if you’re making it up?

It’s normal to be skeptical, and it should be, but it’s all true, and I’ll verify it from the source code.

Explain why from TS source

The point here is not to read the TypeScript source code, so I’ll skip the process and go straight to the result.

For those interested in how to read TypeScript source code, check out my previous post, “Here are my tips for Reading TypeScript source code,” or check out my new TypeScript Type Gymnastic Guide, which explains the principles of various types from source code.

Let’s explain the case of the first union + conditional type:

type Test<T> = T extends number ? 1 : 2;

type res = Test<1 | 'a'>;
Copy the code

TypeScript sets an isDistributive property when processing Conditional types, depending on whether the Type parameter is checkType (the Type on the left).

Because T extends Number’s checkType is T, isDistributive is true, which means it is a distributed condition type.

So what does distributed condition type do?

Each type is passed in as a separate evaluation and the results are merged.

The corresponding source code looks like this:

I don’t even have to explain it here, the annotations are pretty clear. T extends U, right? X: Y when the incoming T is A | B, as A result, extends A U? X : Y)|(B extends U ? X, Y).

This is how distributed conditional types are treated when they encounter union types.

So, the source code goes to the mapTypeWithAlias branch, which does a separate evaluation for each type.

We verified the characteristics of distributed conditional types from the source code!

Let’s look at the second type, when the conditional type + Boolean:

type Test<T> = T extends true ? 1 : 2;

type res = Test<boolean>;
Copy the code

Is it true that Boolean is also a union type?

Debug finds that it has also gone to this branch. Boolean is union or never

Verify:

Flags: each bit indicates a type, and the type is determined by the bitwise and bit operators:

This method takes up less space and computes quickly. Many frameworks use this method to identify types, such as React.

So, from the results of Boolean is joint type, that is true | false. That of course also triggers the distributed conditional type feature, passing in the true and false evaluations separately, and finally merging the results.

Then there is the third type, when the condition type + any:

type Test<T> = T extends true ? 1 : 2;

type res = Test<any>;
Copy the code

Debug will find that instead of going to the mapType branch, it has gone to another branch:

Any is not a union type. We can use flags to check whether any is a union type.

The bitwise sum is 0, which means it’s not union and never.

That is what the results or 1 | 2?

Further down the list, looking at the evaluation logic for the condition type, you’ll find code like this:

Return union of trueType and falseType for ‘any’ since it matches anything Because any matches any type.

That’s why it happened. (I have to say, the TS source code is well commented.)

Then there is the last type, when the condition type + never:

type Test<T> = T extends true ? 1 : 2;

type res = Test<never>;
Copy the code

Why is never returned directly?

Debug will find that never also goes to the mapType branch:

“Never” is not a union type.

The condition does say Union or Never:

“Never” must be special, so don’t worry, let’s move on.

Further down you can see that Union and Never diverged here:

Then mapType returns never directly, which is never:

This is why the result is neither trueType nor falseType, but never.

So far, we have verified the authenticity of the reasons mentioned above through the source code.

conclusion

TypeScript’s type system has some special designs:

Conditional types When checkType (the left type) is a type parameter, it has the property of distributive, that is, when a joint type is passed in, each type is passed separately for evaluation, and the result is merged and returned. This is called a distributed conditional type.

In addition, conditional types return never directly on never and trueType and falseType combinations on any.

And Boolean and joint type, is true | false.

There are a number of special features like this, which I summarize in the TypeScript TypeScript Type Gymnastic Hacks. We provide a thorough overview of TypeScript type programming and how to read TypeScript source code to explain how these types work.