React type definition: @types/ React index.d.ts

In this article, I would like to share these ways of writing, which most people probably do not know:

Extracts the value of the optional index

First, I saw this piece of type logic:

This logic is returned by taking the value of the ref index of index type and putting the extracted type into the local variable R of the infer declaration by pattern matching.

This is what the simplification looks like:

The type that extracts the value of the REF index for Props is returned.

-props [‘ref’] = Props[‘ref’] = Props[‘ref’]

So I changed it to this:

Then I tried it:

This is an optional index. The value is a union type containing undefined.

Exclude will do:

Infer is a more concise way of defining React types. Why is the optional index of infer used to define React types?

And then IT occurred to me, what if this ref value is of type undefined?

I tried:

If the value is of the same type as undefined, Exclude will Exclude undefined and Exclude will never.

So I’ll add undefined:

That will do.

The following two writing methods are compared:

It’s actually simpler to write it like React.

Oh, and what about the judgment up there?

If ref [‘ref’] is not supported, Props[‘ref’] should return never.

Then I came across this note:

In TS 3.0, if the index type has no corresponding index, the type returned is {} instead of never.

This ‘ref’ extends keyof Props is for compatibility.

Those are the two things I learned from this genre:

  • Index access Obj[Key] and infer can both extract and fetch values of an index type. However, when dealing with optional indexes, infer is simpler because the former extracts the type and then processes undefined separately. Infer means undefined.

  • In TS 3.0, if the index type does not have a corresponding index, return {} instead of never. If compatibility requirements are high, use ‘xx’ in keyOf Obj to make compatibility

We’ve learned a lot from this type, but let’s look at the second type:

Index type and handling of any and never

And then I saw this type,

To give it a try, pass in two index types:

Take a look at the results:

What are these? No one can understand them.

This is only because the TS does not calculate the final type, but only when it is used, so we can handle it like this:

The advanced type of Copy constructs a new index type using the syntax of the mapped type, and its function is to Copy an index type exactly as it is.

The Obj constraint is an index type, that is, Record<string, any>. The key stays the same, so key in keyof Obj, and the value stays the same, so Obj[key].

Since the regenerated type is computed, that type should prompt the final result:

So, this type is used for two index types A and B, only those of A are reserved, those of both A and B are optional, and those of B but not of A are optional.

How does this logic work with TS?

Let’s take a look at the built-in advanced types of TS:

Pick

Pick uses the syntax of the mapping type to construct a new index type and filter the index based on the incoming Key:

type Pick<T, K extends keyof T> = { [P in K]: T[P]; };
Copy the code

Under test:

Partial

Partial also constructs a new index type using the syntax of a mapped type, but makes the index optional:

type Partial<T> = { [P inkeyof T]? : T[P]; };Copy the code

Under test:

Extract

Extract is the part that both union types contain, that is, the intersection:

type Extract<T, U> = T extends U ? T : never;
Copy the code

Under test:

Exclude

Exclude excludes the types of union type B from union type A, that is, the difference set:

type Extract<T, U> = T extends U ? T : never;
Copy the code

Under test:

Select select, Partial, Exclude, Extract, and so on

Pick<A, Exclude<keyof A, keyof B>>

Partial<Pick<A, Extract<keyof A, keyof B>>>

Optional: Partial<Pick<B, Exclude<keyof B, keyof A>>>

This clears up the main logic of the genre:

Take the index types of the three parts of the calculation to cross type, and they will be merged together.

So what are the first two judgments?

P extends any and this string extends keyof P, what do they both do?

P extends any this is because a federated type, when presented as a type parameter to the left of a conditional type, passes each type in separately for evaluation and finally combines the results into a federated type. This property is called distributed conditional types.

For example, a union type like this:

type Union = 'a' | 'b' | 'c';
Copy the code

If we want to capitalize the a, we can write it like this:

type UppercaseA<Item extends string> = 
    Item extends 'a' ?  Uppercase<Item> : Item;
Copy the code

Because Item is a type parameter that appears to the left of the condition type and is passed in a union type, the distribution feature is triggered, passing in each type separately for evaluation and merging the result into the union type.

So the purpose of P extends any here is to trigger the union type property so that the type can handle the union type correctly. Otherwise, how to do the calculation after the union type is passed in.

P extends any here can also be replaced with P extends P, it does the same thing.

What does string extends keyof P mean?

This I really want to for a long time, if {a: 1, b: 2} such index type, keyof is the result of the ‘a’ | ‘b’, and if it is an array type, the keyof result is 0 | 1 | ‘length’ | ‘toString |…

What type of keyof results in a string?

All of a sudden, I remembered a knowledge: learn a few days ago with keyof any instead of string | number | symbol more flexible:

And I tried keyof never and got this:

So string extends keyof P rules out any and never!

You can also distinguish “any” from “never.”

So the logic of this type is clear:

The function of this type is to keep only A index, make both A and B index optional, and make only B index optional.

It also handles the case of the union type.

If any or never is passed, no processing is done and the value is returned.

We’ve learned a lot from this genre as well.

conclusion

I took a look at the @types/ React type definition and learned a few things:

  • Infer is more convenient than Obj[key], which requires only Obj[key] extends {XXX? Infer Value: undefined}; infer Value: undefined} and infer Value <Obj[Key]; infer Value: undefined}

  • Ts 3.0 returns {} instead of never, ‘XXX’ in keyof Obj.

  • You can use Pick, Partial, Exclude, Extract and other built-in advanced types to process each part of the index, and then take the cross type to merge it together.

  • The effect of P extends any and P extends P is to trigger the distribution feature of the union type. This extends is required to properly handle the union type. Each type is passed in separately for calculation, and the result is merged into the union type.

  • String extends keyof Obj can determine any and never type, only the results of the two types take keyof is string | nubmer | symbole, contains the string.

And, there’s a little trick:

The ts type is evaluated only when evaluated. If it is an index type, you can use the syntax of a mapping type to create an identical index type, because when used, the evaluation will be performed and the final type will be displayed.

I have to say that the React type definition is pretty well done, taking into account various types of processing and compatibility with lower versions, so there’s a lot to learn from it.