Author: Wang Chong

GitHub: github.com/hubvue

I have systematically studied TypeScript and can use some basic type definitions in a project, but I don’t know much about advanced types. I see that some projects or libraries write advanced types in a completely confused state, and I decide to change this state. Forgetting who said that reading source code is the best way to learn, I decided to find a library that specializes in TypeScript. Two good libraries were recommended by my colleagues: Utility-types and TS-Toolbelt. Considering that there are more utility- typesstars and they are used more, let’s go with it. Then I will interpret ts-Toolbelt.

This article focuses on the types in the mapped-types.ts file.

SetIntersection

The SetIntersection type is used to Extract in the Typescript built-in types API. The SetIntersection type is used to Extract in the same way. The SetIntersection takes the compatible type of type B from type A. Used mostly for union types.

The built-in Extract type is implemented in the same way as the SetIntersection

implementation

type SetIntersection<A, B> = A extends B ? A : never
Copy the code

The sample

type SetIntersectionResult = SetIntersection<'a' | 'b' | 'c'.'c' | 'b'> // 'b' | 'c'
Copy the code

How did the above example result come about? We all know that a conditional type becomes a distributed conditional type when applied to a union type.

'a' | 'b' | 'c' extends 'c' | 'b' ? 'a' | 'b' | 'c' : never= >
('a' extends 'c' | 'b' ? 'a' : never) |
('b' extends 'c' | 'b' ? 'b' : never) |
('c' extends 'c' | 'b' ? 'c' : never) = >
never | 'b' | 'c'= >'b' | 'c'
Copy the code

SetDifference

Similar to TypeScript’s built-in Exclude type, the SetDifference type is used to retrieve types of type A that are incompatible with type B.

implementation

type SetDifference<A, B> = A extends B ? never : A
Copy the code

The sample

type SetDifferenceResult = SetDifference<'a' | 'b' | 'c'.'b'> // 'a' | 'c'
Copy the code

How did the above example result come about? In fact, and the last type of operation results are roughly the same, combined with examples and source code explanation below:

'a' | 'b' | 'c' extends 'b' ? never : 'a' | 'b' | 'c'= > ('a' extends 'b' ? never : 'a') |
('b' extends 'b' ? never : 'b') |
('c' extends 'b' ? never : 'c') = >
'a' | never | 'c'= >'a' | 'c'
Copy the code

There is also A type SetComplement in the source code, but it is implemented in the same way as SetDifference, except that generic B must be A subtype of generic A.

type SetComplement<A, A1 extends A> = A extends A1 ? never : A
Copy the code

SymmetricDifference

SymmetricDifference: SymmetricDifference: SymmetricDifference: SymmetricDifference: SymmetricDifference: SymmetricDifference: SymmetricDifference: SymmetricDifference

implementation

type SymmetricDifference<A, B> = SetDifference<A | B, SetIntersection<A, B>>
Copy the code

emmmm… A little convoluted, look at 🌰

type SymmtricDifferenceResult = SymmetricDifference<
  '1' | '2' | '3'.'2' | '3' | '4'
> / / '1' | '4'
Copy the code

And sets the example of two types: ‘1’ | ‘2’ | ‘3’ | ‘4’, intersection for ‘2’ | ‘3’, so the intersection in complement on and set to ‘1’ | ‘4’.

How do you do that? As can be seen from the source code, we use the SetDifference and SetIntersection types, which were previously implemented and combined to form a more powerful type.

Source of the solution is this: by A | B to get to A and B type and set, and then through SetIntersection type is available at the intersection between A and B type, and then use SetDifference types to complement the results.

NonUndefined

The NonUndefined type is used to filter out undefined types from union types.

implementation

type NonUndefined<T> = T extends undefined ? never : T
Copy the code

Source code in the implementation is above such, the following is to borrow SetDifference implementation.

type NonUndefined<T> = SetDifference<T, undefined>
Copy the code

The sample

type NonUndefinedResult = NonUndefined<string | null | undefined> // string | null
Copy the code

To see this, you need to set strictNullChecks to true in tsconfig.json to strictly check for null. If not, ts defaults to null compatibility with undefined, so null will be filtered out.

FunctionKeys

FunctionKeys is used to get the key of an object type whose value is a function.

implementation

type FunctionKeys<T extends object> = {
  [K inkeyof T]-? : NonUndefined<T[K]>extends Function ? K : never
}[keyof T]
Copy the code

Source code is above this implementation, but some defects, in the analysis of the principle of the time to say why the defects.

The sample

type MixedProps = {
  name: string
  setName: (name: string) = > voidsomeKeys? :stringsomeFn? :(. args:any) = > any
  undef: undefined
  unNull: null
}
type FunctionKeysResult = FunctionKeys<MixedProps> //"setName" | "someFn" | "undef" 
Copy the code

Yi, should not be “elegantly-named setName” | “someFn” yao, why have two? Let’s first analyze how this type is implemented and look for bugs in the analysis.

FunctionKeys accepts an object type, so you can use the index query operator to iterate over each key of the object type, filtering out undefined first through NonUndefined, then extends Function, If the key type is undefined, it will be resolved by NonUndefined to never, whereas the Function type is compatible with never. So the undef is preserved.

So I changed on the basis of the source code.

type FunctionKeys<T extends object> = {
  [P inkeyof T]-? : SetIntersection<NonNullable<T[P]>,Function> extends never
    ? never
    : P
}[keyof T]
Copy the code

The specific idea is to first convert the value type of key whose value type is undefined and null to never in the traversal process, and then convert the value type of key whose value type is not Function type to never, because the type of never is only compatible with itself. Therefore, determine whether the value type is compatible with the never type, filter out all keys whose value is never, and finally obtain the union type of the value type through the index query operator.

NonFunctionKeys

NonFunctionKeys is used to get the key of an object type whose value is not a function

implementation

type NonFunctionKeys<T extends Object> = {
  [P inkeyof T]-? : NonUndefined<T[P]>extends Function ? never : P
}[keyof T]
Copy the code

The sample

type NonFunctionKeysResult = NonFunctionKeys<MixedProps> //"name" | "someKeys" | "unNull"
Copy the code

After analyzing the FunctionKeys type, the NonFunctionKeys type should be easy to understand.

In the process of traversing the object type, use NonUndefined to filter out the key whose value type is undefined, then filter out the key whose value type is function, and finally obtain the union type of the value type through the index query operator.

IfEquals

IfEquals is an auxiliary type function that determines whether two types are the same.

implementation

type IfEquals<X, Y, A = X, B = never> = (<T>() = > T extends X ? 1 : 2) extends <
  T
>() = > T extends Y ? 1 : 2
  ? A
  : B
Copy the code

Now, if you know anything about TS you might be wondering, isn’t it just two-way extends? What is this thing? 🤔 ️

I think what you’re talking about is two-way extends.

type Same<X, Y> = X extends Y ? (Y extends X ? true : false) : false
Copy the code

There is a flaw in writing the Same type function. There is no way to infer whether two types are absolutely the Same, such as an object type with the Same structure but different attribute modifiers.

type X = {
  name: string
  age: number
}
type Y = {
  readonly name: string
  age: number
}
Copy the code

The above two Same type functions cannot be inferred, in which case you must use IfEquals.

The sample

type SameResult = Same<X, Y> //true
type IfEqualsResult = IfEquals<X, Y> //never
Copy the code

The core of the IfEquals type function is the use of delayed conditional types that rely on consistency checks of internal types for compatibility inferences. IfEquals relies on at least two generic parameters, X and Y. After passing in the X and Y generic parameters, the type is inferred. If the result can be inferred, the final type is returned, otherwise the inference process is delayed until the confirmed type parameters are passed in.

Like the IfEquals type function, constructing a delay condition type is as simple as building a function type and building the return value of the function into a condition type that depends on the generic parameters.

type DeferConditionalType = <T>(value: T) = > T extends string ? number : boolean
Copy the code

When you use the DeferConditionalType generic, you infer the type of the return value from the delay of the generic parameter passed in.

WriteableKeys

WriteableKeys is used to get all writable keys in an object type.

implementation

export type WriteableKeys<T extends object> = {
  [P inkeyof T]-? : IfEquals< { [Qin P]: T[P] },
    { -readonly [Q in P]: T[P] },
    P
  >
}[keyof T]
Copy the code

The sample

type Props = { readonly foo: string; bar: number }

type WriteableKeysResult = WriteableKeys<Props> // "bar"
Copy the code

The IfEquals function is used in the source code. Now that we know that IfEquals is used to determine whether two types are strictly equal (see IfEquals for details), this should be easier to do.

In the process of iterating over the key object, we construct two objects, the one with the original key construct and the one without the readonly modifier, and pass in the third argument, key, as the return value of the function matching the same type. So the end result is that all the readonly keys have a value type of never, the rest of the keys have a value type of key itself, and then the index type access operator gets the union type of all the key’s value types.

ReadonlyKeys

ReadonlyKeys Is used to obtain all keys of an object type that are modified by readonly.

implementation

export type ReadonlyKeys<T extends object> = {
  [P inkeyof T]-? : IfEquals< { [Qin P]: T[P] },
    { -readonly [Q in P]: T[P] },
    never,
    P
  >
}[keyof T]
Copy the code

The sample

type Props = { readonly foo: string; bar: number }

type ReadonlyKeysResult = ReadonlyKeys<Props> // "foo"
Copy the code

The implementation of ReadonlyKeys is basically the same as that of WriteableKeys, except for the third and fourth arguments to the IfEquals function. In WriteableKeys, the third argument is key, and the fourth argument is never by default, which is reversed in ReadonlyKeys. The reason is that when two types match, they are considered strictly the same. If the current key is not modified by readonly, return key in WriteableKeys and never in ReadonlyKeys. When two types fail to match, they are assumed to be different.

RequiredKeys RequiredKeys is used to obtain all RequiredKeys in an object type.

implementation

export type RequiredKeys<T extends object> = {
  [P inkeyof T]-? : {}extends Pick<T, P> ? never : P
}[keyof T]
Copy the code

The sample

type RequiredProps = {
  req: number
  reqUndef: number | undefinedopt? :stringoptUndef? :number | undefined
}

type RequiredKeysResult = RequiredKeys<RequiredProps> //"req" | "reqUndef"
Copy the code

RequiredKeys uses Pick. First, let’s talk about what Pick does

Pick is a built-in Typescript generic function that takes two T, U, the first argument T is an object type, the second argument U is a union type, and U extends keyof T. Pick is used to filter out keys in the generic T that are not compatible with U.

Such as:

type Props = {
  req: number
  reqUndef: number | undefinedopt? :stringoptUndef? :number | undefined
}
type result = Pick<Props, 'req' | 'opt'> //  {req: number,opt?: string}
Copy the code

Returning to the RequiredKeys function, when iterating over the key of the generic T, the empty {} object is used to extend the handled key(in this case, an object containing only keys). If the current key is optional, it must be compatible. Return never, which is not what we want, otherwise it is mandatory to return the current key.

OptionalKeys

OptionalKeys is used to get all OptionalKeys on the object type.

implementation

export type OptionalKeys<T extends object> = {
  [P inkeyof T]-? : {}extends Pick<T, P> ? P : never
}[keyof T]
Copy the code

The sample

type RequiredProps = {
  req: number
  reqUndef: number | undefinedopt? :stringoptUndef? :number | undefined
}
type OptionalKeysResult = OptionalKeys<RequiredProps> // "opt" | "optUndef"
Copy the code

OptionalKeys is implemented in much the same way as RequiredKeys, except that the value of the condition type is the same. See the RequiredKeys implementation analysis for details.

PickByValue

When we read the RequiredKeys function we talked about the Pick built-in type function, which filters the key of an object by key, and PickByValue by value type.

implementation

export type PickByValue<T, K> = Pick<
  T,
  {
    [P inkeyof T]-? : T[P]extends K ? P : never
  }[keyof T]
>
Copy the code

The sample

type PickByValueProps = {
  req: number
  reqUndef: number | undefinedopt? :string
}

type PickByValueResult = PickByValue<PickByValueProps, number> //{req: number; reqUndef: number | undefined; }
Copy the code

Let’s go back to PickByValue with the result. In this case, the first result we want is to filter out all keys whose value type is compatible with number. Because it’s filtering, the outermost layer of PickByValue must be picked.

typePickByValue<T, K> = Pick<T, ... >Copy the code

So for now, all we need to do to implement this function is take care of the second argument. Since the second argument is necessarily a subset of keyof T, what we need to do is derive a compatible keyof type value from the type of value. The next step is necessarily to iterate over the key and get the final subset through {}[keyof T].

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

In the process of traversal to determine whether the type of T[P] is compatible with K, the end result is what the implementation looks like.

PickByValueExact

PickByValueExact is a strict version of PickByValue

implementation

export type PickByValueExact<T, ValueType> = Pick<
  T,
  {
    [Key inkeyof T]-? : [ValueType]extends [T[Key]]
      ? [T[Key]] extends [ValueType]
        ? Key
        : never
      : never
  }[keyof T]
>
Copy the code

The source code is a two-way extends, which feels a little more restrictive with IfEquals.

export type PickByValueExact<T, K> = Pick<
  T,
  {
    [P inkeyof T]-? : IfEquals<[K], [T[P]], P> }[keyof T] >Copy the code

The sample

type PickByValueProps = {
  req: number
  reqUndef: number | stringopt? :string
}

type PickByValueExactResult = PickByValueExact<PickByValueProps, number> //{req: number; }
Copy the code

The implementation idea is roughly the same as PickByValue. The difference is that PickByValueExact uses IfEquals for strict matching.

Omit

The effect of Omit is to reverse Pick, remove the key in generic A that can match generic B.

implementation

export type Omit<A, B extends keyof A> = Pick<A, Exclude<keyof A, B>>
Copy the code
type OmitProps = {
  name: string
  age: number
  visible: boolean
  sex: string | number
}

/ / {
// name: string;
// visible: boolean;
// sex: string | number;
// }
type OmitResult = Omit<OmitProps, 'age'>
Copy the code

Reverse Pick can be done with the help of Pick, just by processing the second parameter of Pick. Way is to use Exclude generic function of A and B take keyof complement, access to A generic object A filtered in compatible with generic B.

OmitByValue

Reverse PickByValue, PickByValue is include only, OmitByValue is filter only.

implementation

export type OmitByValue<T, U> = Pick<
  T,
  {
    [P in keyof T]: T[P] extends U ? never : P
  }
>
Copy the code

The sample

type OmitProps = {
  name: string
  age: number
  visible: boolean
  sex: string | number
}
/ / {
// age: number;
// visible: boolean;
// sex: string | number;
// }
type OmitByValueResult = OmitByValue<OmitProps, string>
Copy the code

Similar to PickByValue, you can reverse the extends result by swapping its position. See the analysis of PickByValue for details.

OmitByValueExact

implementation

export type OmitByValueExact<T, ValueType> = Pick<
  T,
  {
    [Key inkeyof T]-? : [ValueType]extends [T[Key]]
      ? [T[Key]] extends [ValueType]
        ? never
        : Key
      : Key
  }[keyof T]
>
Copy the code

Two-way extends is used in the source code to determine whether two types are strictly compatible, which I’ve done here with the IfEquals function.

export type OmitByValueExact<A, B> = Pick<
  A,
  {
    [P inkeyof A]-? : IfEquals<A[P], B,never, P>
  }[keyof A]
>
Copy the code

The sample

type OmitProps = {
  name: string
  age: number
  visible: boolean
  sex: string | number
}
/ / {
// name: string
// age: number
// visible: boolean
// }
type OmitByValueExactResult = OmitByValueExact<OmitProps, string | number>
Copy the code

OmitByValueExact is implemented in a similar way to PickByValueExact, except that the IfEquals function returns the exact exact value in a different position. For details, see the implementation of PickByValueExact.

Intersection

Intersection is used to get the Intersection of the object type key.

implementation

export type Intersection<T extends object, U extends object> = Pick<
  T,
  Extract<keyof T, keyof U> & Extract<keyof U, keyof T>
>
Copy the code

The sample

type IntersectionProps = {
  name: string
  age: number
  visible: boolean
  value: number
}
type DefaultProps = { age: number; value: number }
/ / {
// age: number;
// value: number;
// }
type IntersectionResult = Intersection<IntersectionProps, DefaultProps>
Copy the code

The Intersection function takes two

object types. The result is A Pick at the Intersection of two object types key on A. So we just solve for the intersection of two object types, key, and then Pick A.
,b>

To find the intersection, use the Extract generic function, convert A and B to the union type using the index operator, then use Extract to find the intersection of the two union types, and then Pick A.

Personally, I don’t think the second Extract is necessary because the intersection of the two combination types is the same as the one that came first.

Diff

The Diff function takes two generic variables T and U, both of which are object types, to get the complement of the generic U on the generic T.

implementation

export type Diff<T extends object, U extends object> = Pick<
  T,
  Exclude<keyof T, keyof U>
>
Copy the code

The sample

type Props = {
  name: string
  age: number
  visible: boolean
  value: number
}
type Props2 = { age: number; value: number }
/ / {
// name: string;
// visible: boolean;
// }
type DiffResult = Diff<Props, Props2>
Copy the code

We should already know from the use of the Pick function in the above type functions that Pick is used to handle an object type and return a subset of the object type, so the complement should start with the key of both object types. In the beginning, we mentioned that Exclude is used to find the complement of two union types, so we can get the union type of key of two object types through the index type modifier, then we can get the complement by Exclude, and finally we can get the subset of T by Pick.

Overwrite

Overwrite takes two generic parameters, T and U, both of object type, and overwrites attributes in T if attributes in U also exist in T.

implementation

export type Overwrite<
  T extends object,
  U extends Object,
  I = Diff<T, U> & Intersection<U, T>
> = Pick<I, keyof I>
Copy the code

The sample

type Props1 = { name: string; age: number; visible: boolean }
type Props2 = { age: string; other: string }

/ / {
// name: string
// age: string
// visible: boolean
// }
type OverwriteResult = Overwrite<Props1, Props2>
Copy the code

With knowledge of Diff and Intersection, Overwrite is a piece of cake. We know that Diff is used to get the complement of two generic parameters, and Intersection is used to get the Intersection of two generic parameters, finally synthesizing the crossover type.

Intersection

. Diff

& Intersection

is all you need.
,>
,>
,>

Let’s look at the type inference results in both cases.

  1. Use a Pick
type OverwriteResult = Overwrite<Props1, Props2>
/ / = >
/ / {
// name: string
// age: string
// visible: boolean
// }
Copy the code
  1. Do not use a Pick
export type Overwrite<T extends object, U extends Object> = Diff<T, U> &
  Intersection<U, T>
type OverwriteResult = Overwrite<Props1, Props2>
// => Pick<OverwriteProps, "name" | "visible"> & Pick<NewProps, "age">
Copy the code

As you can see, the results of not using Pick are user-unfriendly, and you cannot see the results of type inference directly from the IDE.

Assign

Assign is more powerful than Overwrite. It takes two generic parameters, T and U, and both are of object type. It overwrites attributes in U if they exist in T, and adds them if they don’t.

implementation

export type Assign<
  T extends object,
  U extends object,
  I = Diff<T, U> & Intersection<U, T> & Diff<U, T>
> = Pick<I, keyof I>
Copy the code

The sample

type Props1 = { name: string; age: number; visible: boolean }
type Props2 = { age: string; other: string }
/ / {
// name: string;
// age: string;
// visible: boolean;
// other: string;
// }
type AssignResult = Assign<Props1, Props2>
Copy the code

Diff

; Overwrite

; Assign only needs to merge attributes that do not exist on T to T, so you can Diff

to get attributes that do not exist on T, and then cross with the preceding sum.
,>
,>
,>

Unionize

Unionize takes a generic parameter and is an object type. It is used to convert the object type to the union type of the individual key object.

implementation

export type Unionize<T extends object> = {
  [P in keyof T]: { [Q in P]: T[P] }
}[keyof T]
Copy the code

The sample

type Props = { name: string; age: number; visible: boolean }
/ / {
// name: string;
// } | {
// age: number;
// } | {
// visible: boolean;
// }
type UnionizeResult = Unionize<Props>
Copy the code

It’s kind of confusing at first, but if you think about it, you’ve written a lot of this, where you go through the key of the object, you construct the value into an object, and then you take the union type of all the values through the index operator.

PromiseType

PromiseType Specifies the generic type used to obtain a Promise.

implementation

export type PromiseType<T extends Promise<unknown>> = T extends Promise<infer V>
  ? V
  : never
Copy the code

The sample

// string
type PromiseTypeResult = PromiseType<Promise<string>>
Copy the code

Infer is used in PromiseType. The function of infer is to make delay inference in condition types. Infer can achieve powerful functions by being excellent.

PromiseType (s) PromiseType (s) PromiseType (s) PromiseType (s) PromiseType (s) PromiseType (s)

Think about it. What if you deeply parse Promise generics? 🤔

DeepReadonly

In utility-types, the recursive types of DeepX are basically the same. The logic of X has been analyzed above. The main analysis is Deep logic.

implementation

export type DeepReadonly<T> = T extends ((. args:any[]) = > any) | Primitive
  ? T
  : T extends _DeepReadonlyArray<infer U>
  ? _DeepReadonlyArray<U>
  : T extends _DeepReadonlyObject<infer V>
  ? _DeepReadonlyObject<V>
  : T
export interface _DeepReadonlyArray<T> extends ReadonlyArray<DeepReadonly<T>> {}
export type _DeepReadonlyObject<T> = {
  readonly [P in keyof T]: DeepReadonly<T[P]>
}
Copy the code

The sample

typeProps = { first? : { second? : { name? :string}}}type DeepReadonlyResult = DeepReadonly<Props>
Copy the code

The array and object types are processed separately in the source code. You can see that the _DeepReadonlyObject generic function calls DeepReadonly again for recursive parsing during the process of traversing T.

Think about it, why are there no circular references? 🤔

Optional

Optional accepts two generic parameters, T and K. T is an object type, and K is a subset of all key union types of T. It converts the attributes of T compatible with K to Optional.

implementation

export type Optional<
  T extends object,
  K extends keyof T = keyof T,
  I = Omit<T, K> & Partial<Pick<T, K>>
> = Pick<I, keyof I>
Copy the code

The sample

type Props = {
  first: string
  second: number
  third: boolean
}
/ / {
// first? : string
// second? : number
// third: boolean
// }
type OptionsalResult = Optional<Props, 'first' | 'second'>
Copy the code

So let’s think about what we need to do to make this happen.

Since we’re dealing with some attributes, we can delete them first and then merge them when we’re done, which is exactly what the source code does.

If you read it in order, it’s already done what the generalizations of Omit and Pick do, so you can use Omit first. Then use Pick to keep only the properties to be processed and use the Partial generic function, and finally use the crossover type to combine the two.

ValuesType

ValuesType takes a generic parameter, which can be an array or an object, to get the associative type of the value. Array is more of a tuple here, since all elements in a normal array are of the same type, there is no need for union.

implementation

export type ValuesType<
  T extends Array<any> | ReadonlyArray<any> | ArrayLike<any> | object
> = T extends Array<any> | ReadonlyArray<any> | ArrayLike<any>? T[number]
  : T extends object
  ? T[keyof T]
  : never
Copy the code

The sample

type Props = {
  first: string
  second: number
  third: boolean
}
// string | number | boolean
type ValuesTypeResult = ValuesType<Props>
Copy the code

ValuesType processing parameters are divided into two parts: array processing and object processing. The use of T[number] for arrays is elegant and the easiest way to convert tuples to union types; For objects, we use the index operator.

ArgumentsRequired

ArgumentsRequired, like Optional, is used to make certain attributes of an object mandatory

implementation

export type ArgumentsRequired<
  T extends object,
  K extends keyof T = keyof T,
  I = Omit<T, K> & Required<Pick<T, K>>
> = Pick<I, keyof I>
Copy the code

The sample

typeProps = { name? :stringage? :numbervisible? :boolean
}
/ / {
// name: string
// age: number
// visible: boolean
// }
type ArgumentsRequiredResult = ArgumentsRequired<Props>
Copy the code

The implementation of the resolution can see Optional, here will not say much.

TupleToUnion

A particularly simple approach has been mentioned in ValuesType. There is another way to learn.

Tuple types are compatible with array types in the type system.

// 'true'
type ret = [number.string] extends Array<any>?'true' : 'false'
Copy the code

So you can use infer to infer the generic type of an array.

implementation

export type TupleToUnion<T extends any[]> = T extends Array<infer U> ? U : never
Copy the code

The sample

// string | number
type TupleToUnionResult = TupleToUnion<[string.number] >Copy the code

UnionToIntersection

UnionToIntersection is used to convert a union type to an intersection type

implementation

export type UnionToIntersection<T> = (T extends any
? (arg: T) = > void
: never) extends (arg: infer V) => void
  ? V
  : never
Copy the code

The sample

type UnionToIntersectionResult = UnionToIntersection<
  { name: string } | { age: number } | { visible: boolean }
>
Copy the code

UnionToIntersection is a generic function that needs to be understood. It uses the TypeScript type system concept that multiple candidate types of variables of the same type will be inferred to be intersecting types. This is the inverse knowledge of TS type system function parameter positions. Contravariant and covariant this article says very clearly, can in-depth understanding.

With the TS type system in mind, UnionToIntersection is easy to understand. It is known that the generic function accepts a union type, and then builds multiple candidate types of variables of the same type through distributed conditional types, and then uses delayed inference to obtain the type of V.

conclusion

Reading the advanced types in utility-types, I realized that TypeScript is much more than just assigning a type to a function’s argument position. It’s important to make good use of TypeScript’s type inference capabilities. Making a function type-inferential can actually write a type longer than the code it runs. To make the code more stable and better understood by your colleagues, we sometimes have to do the same 😭. Don’t make your TypeScript AnyScript.