Hello, everyone. I'm zero one. I'm sure you've seen all kinds of coquettish TS writing methods when reading the code written by colleagues or the code of excellent open source libraries. You can't understand it without taking some time. If it were us, you might finish any directly, but when the project volume becomes larger, you will find that these TS coquettish operations are really important, Because it can help you do static type checking very well
Today, let's introduce a fancy and practical TS type - TS type filtering, which can be seen in other open source libraries
self-introduction
TS type filtering, the English name (I took it myself) is FilterConditionally, which is its complete appearance 👇
type FilterConditionally<Source, Condition> = Pick< Source, { [K in keyof Source]: Source[K] extends Condition ? K : never }[keyof Source] >;
Although it seems very complex, it is actually very useful. It can filter out what you want from an object type, such as:
interface Example { a: string; // ✅ b: string; // ✅ c: number; // ❌ d: boolean; // ❌ } type NewType = FilterConditionally<Sample, string> /* NewType The final result is: { a: string; b: string } */
I believe you have this type of function, and you also want to understand it. It doesn't matter. Next, introduce it step by step from the inside out. You must fully understand it. If you can't understand it, spray me in the comment area (I'm talking about playing ~)
Step by step introduction
There are many knowledge points involved. I'm afraid some students who are not familiar with TS will be confused. Let's introduce some common basic knowledge points first
Appetizer
It won't take you much time. You can adjust it directly
keyof
The name of the keyword keyof is called the index type query operator. Its function is as straightforward as its literal meaning: the key value of xx
interface Example { a: string; b: string; c: number; d: boolean; } type Keys = keyof Example // Equivalent to type keys = 'a' | 'B' | 'C' |'d '
You can simply understand keyof as Object.keys in JavaScript
in
The keyword in can traverse enumeration types, such as:
type Keys = 'a' | 'b' | 'c' | 'd' type Obj = { [T in Keys]: string; // Traverse Keys and assign each key to string type } /* Equivalent to type Obj = { a: string; b: string; c: string; d: string; } */
You can simply understand in as the function of for...in in JavaScript
Conditional
The second knowledge point is conditional judgment, such as:
interface A {} interface B extends A {} // B inherits from A // Does B inherit from A? If yes, it is of type number; If not, it is of type string type C = B extends A ? number : string // Equivalent to type C = number // Does A inherit from B? If yes, it is of type number; If not, it is of type string type D = A extends B ? number : string // Equivalent to type D = string
Can you put a extends B? Number: string is simply understood as a ternary operator in JavaScript
generic paradigm
I won't introduce generics any more. If you don't know much about generics, you can read it directly TS document - Generic
Dinner begins
Just after introducing the "appetizer", strike while the iron is hot and see a simple type
type MarkUnwantedTypesAsNever<Source, Condition> ={ [K in keyof Source]: Source[K] extends Condition ? K : never }
In a word, the function of this type is to traverse an object type and mark the unwanted type as never
for instance 🌰
interface Example { a: string; // ✅ b: string; // ✅ c: number; // ❌ d: boolean; // ❌ } // I only want the key of string type in the Example type, and the non string is marked as never type MyType = MarkUnwantedTypesAsNever<Example, string> /* Equivalent to: type MyType = { a: 'a'; b: 'b'; c: never; d: never; } */
Let's talk about the details a little bit. The [K in keyof Example] traverses the object type of Example, and then uses the condition to judge whether the Example [k] extends string? K: never assigns a value to the corresponding key value. Assuming that the first key value of traversal is a, then Example[K] = Example[a] = string, which is String Extensions string? ' A ': never, string must inherit from string, so there is such a result
At this time, everyone was surprised. Why did you make the type like this?? Isn't the final result we want to get a type of {a:string; b:string}? Don't worry, there are other operations behind
Let's look at a small knowledge point of index provider properties
type Value = {name: "zero2one"}["name"] // Equivalent to type Value = "zero2one"
You can simply understand it as the value corresponding to a key of the access object in JavaScript
There is another case in TS:
type Value = { name: "zero2one"; age: 23 }["name" | "age"] // Equivalent to type Value = "zero2one" | 23
The key value with the value of never cannot be accessed:
type Value = { name: "zero2one"; age: never }["name" | "age"] // Equivalent to type Value = "zero2one"
So let's look at more complex types
type MarkUnwantedTypesAsNever<Source, Condition> ={ [K in keyof Source]: Source[K] extends Condition ? K : never }[keyof Source]
We skillfully use the keyof keyword to traverse and access all interface properties
// Let me borrow the results of the example just now type MyType = { a: 'a'; b: 'b'; c: never; d: never; }['a' | 'b' | 'c' | 'd'] /* Equivalent to: type MyType = 'a' | 'b' */
So far, what we have done is to filter out the key values of the desired type in the target object type
Don't worry, don't worry, it's one step away from success
The last one is Pick. This type is built-in in TS. let's have a brief understanding of its role
// Implementation of Pick type type Pick<T, K extends keyof T> = { [P in K]: T[P]; }
You don't need to read its implementation in detail. You just need to know that the function of Pick is to filter out some properties specified in type T
Take a simple example:
interface A { a: 1; b: 2; c: 3; d: 4; } type C = Pick<A, 'a' | 'c'> // Equivalent to type C = {A: 1; C: 3}
Yes, it's that simple. Well, you can see the final BOSS
Finally, you can filter the corresponding attributes from the Source. Back to the specific example in this article, the value in the red box in the figure has been obtained as type MyType = 'a' | 'b', so just Pick it at last
interface Example { a: string; b: string; c: number; d: boolean; } // Results obtained above type MyType = 'a' | 'b' type Result = Pick<Example, MyType> // Equivalent to type result = {A: string; B: String} // ----The above is equivalent to ----// interface Example { a: string; // ✅ b: string; // ✅ c: number; // ❌ d: boolean; // ❌ } type NewType = FilterConditionally<Sample, string> /* NewType The final result is: { a: string; b: string } */
This is the whole process of obtaining the results at the beginning of the article
Practical application examples
As stated in the title of this article, TS type filtering is very common in many excellent open source libraries. For example, we are familiar with React:
type ElementType<P = any> = { [K in keyof JSX.IntrinsicElements]: P extends JSX.IntrinsicElements[K] ? K : never }[keyof JSX.IntrinsicElements] | ComponentType<P>;
last
There are too many scenarios like TS type filtering in the open source library. I hope you can easily understand them in the future. If you read in front of the screen is the back-end, you may also see it in the back-end open source framework source code~
If this article is helpful to you, please give a praise. Thank you very much~
I'm zero one, sharing technology, not just the front end!