Recommended reading before doing gymnastics:

  1. The official documentation
  2. Big guy’s article, benefit a lot

Before I was writing my own library, I felt that MY TS writing was too bad, so I decided to improve TS. I have to say that only by mastering these poses can I write better type design in application

Original project address: github.com/type-challe…

Besides, I’m very long, you can bear with me, if you really can’t help it, you can use it after Mark gets up

simple

Implement a pick

Implement the built-in Pick

for TS, but do not use it.
,>

type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}
interface Todo {
  title: string
  description: string
  completed: boolean
}

/* A = { title: string; completed: boolean; } * /
type A = MyPick<Todo, 'title' | 'completed'>
Copy the code

Realize the Readonly

type MyReadonly<T> = {
  readonly [P in keyof T]: T[P]
}
Copy the code

Tuples are converted to objects

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type TupleToObject<T extends readonly string[]> = {
  [P in T[number]]:P
}

const result: TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}
Copy the code

Be careful to add readonly because as const generates the following type:

const tuple: readonly ["tesla"."model 3"."model X"."model Y"]
Copy the code

Const assertion document: www.typescriptlang.org/docs/handbo…

The first element

Implement a generic First

that takes an array T and returns the type of its First element.

type First<T extends any[]> = T extends [infer F, ...infer R] ? F : never
Copy the code

Tuples document: www.typescriptlang.org/docs/handbo…

Infer document: www.typescriptlang.org/docs/handbo…

Gets the length of the element

type Length<T extends readonly any[]> = T['length']
Copy the code

A tuple type is another Array type that knows exactly how many elements it contains and which types it contains at a particular location

Extend the performance of readOnly

Array, element plus readonly for common form parent set object attribute redonly does not affect type compatibility

type A = [string]
type RA = Readonly<A>

type B = string[]
type RB = Readonly<B>

type IsExtends<T, Y> = T extends Y ? true : false

type AExtendsRA = IsExtends<A, RA> //true

type RAExtendsA = IsExtends<RA, A> //false

type BExtendsRA = IsExtends<B, RB> // true

type RBExtendsB = IsExtends<RB, B> // false

type C = {
  name: string
}
type RC = Readonly<C>
type CExtendsRC = IsExtends<C, RC> // true
type RCExtendsC = IsExtends<RC, C> // true
Copy the code

Object read-only properties do not affect type compatibility:

www.typescriptlang.org/docs/handbo…

Stackoverflow.com/questions/5…

Arrays and tuples are read-only:

Github.com/Microsoft/T…

Exclude

Implement the built-in Exclude <T, U>

type MyExclude<T, K> = T extends K ? never : T
Copy the code

Awaited

Get the ExampleType in the Promise

type Awaited<T extends Promise<any>> = T extends PromiseLike<infer L> ? L :never
Copy the code

If

example:

type A = If<true.'a'.'b'>  // expected to be 'a'
type B = If<false.'a'.'b'> // expected to be 'b'
Copy the code
type If<C extends boolean, T, F> = C extends true ? T : F
Copy the code

Concat

example:

type Result = Concat<[1], [2] >// expected to be [1, 2]
Copy the code
type Concat<T extends any[], U extends any[]> = [...T, ...U]
Copy the code

Includes

example :

type isPillarMen = Includes<['Kars'.'Esidisi'.'Wamuu'.'Santana'].'Dio'> // expected to be `false`
Copy the code
// Convert a tuple to an object with value true
type Includes<T extends any[], U> = {
  [K in T[number]] :true
}[U] extends true
  ? true
  : false
Copy the code

medium

Gets the function return type

Implement TypeScript’s ReturnType

paradigm without using ReturnType.

type MyReturnType<T> = T extends(... argv:any[]) => infer T ? T :never
Copy the code

Implement Omit

// one
type MyPick<T, K extends keyof T> = {
  [P in K]: T[P]
}
type MyExclude<T, K> = T extends K ? never : T
type MyOmit<T, K> = MyPick<T, MyExclude<keyof T, K>>


// two
type MyExclude<T, K> = T extends K ? never : T
type MyOmit<T, K> = {
  [P in keyof T as P extends MyExclude<keyof T, K> ? P : never]: T[P]
}
Copy the code

Realize the Readonly 2

Implement a generic MyReadonly2

with two types of arguments T and K.
,>

K specifies the property set of T that should be set to Readonly. If K is not provided, all properties should be made read-only, just like normal Readonly

.

type MyReadonly2<T, K=keyof T> = {
  [P in keyof T as P extends K ? never : P]: T[P]
} &
  {
    readonly [P in keyof T as P extends K ? P : never]: T[P]
  }

// upgrade
type MyReadonly2<T, K extends keyof T = keyof T> = {
    readonly [P in K]: T[P]
  } & T;
Copy the code

The depth of the Readonly

Implement a generic DeepReadonly

that recursively sets each parameter of the object and its children to read-only.

type IsObjectLiteral<T> = keyof T extends never ? false : true

type DeepReadonly<T> = {
  readonly [P in keyof T]: IsObjectLiteral<T[P]> extends true
    ? DeepReadonly<T[P]>
    : T[P]
}
Copy the code

Tuple to set

example:

type Arr = ['1'.'2'.'3']

const a: TupleToUnion<Arr> // expected to be '1' | '2' | '3'
Copy the code
type TupleToUnion<T extends any[]> = T[number]
Copy the code

Seriable constructor

example:

declare const config: Chainable

const result = config
  .option('foo'.123)
  .option('name'.'type-challenges')
  .option('bar', { value: 'Hello World' })
  .get()

// The expected result type is:
interface Result {
  foo: number
  name: string
  bar: {
    value: string}}Copy the code
type Chainable<P = {}> = {
  option<K extends string, T>
    (
      key: K extends keyof P ? never : K,
      value: T
    ): Chainable<P & {[key in K]: T}>
  get(): P
}
Copy the code

The last element

Implement a generic Last

that takes an array T and returns the type of its Last element.

example:

type arr1 = ['a'.'b'.'c']
type arr2 = [3.2.1]

type tail1 = Last<arr1> // expected to be 'c'
type tail2 = Last<arr2> // expected to be 1
Copy the code
type Last<T extends any[]> = T extends [...any[], infer Latest] ? Latest : never
Copy the code

Out of the heap

Implement a generic Pop

that takes an array T and returns an array without the last element. example:

type arr1 = ['a'.'b'.'c'.'d']
type arr2 = [3.2.1]

type re1 = Pop<arr1> // expected to be ['a', 'b', 'c']
type re2 = Pop<arr2> // expected to be [3, 2]
Copy the code
type Pop<T extends any[]> = T extends [... infer R, infer L] ? R :never
Copy the code

Promise.all

Implement PromiseAll, which accepts an array of PromiseLike objects and returns a Promise

, where T is the parsed array of results.

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise<string> ((resolve, reject) = > {
  setTimeout(resolve, 100.'foo');
});

// expected to be `Promise<[number, number, string]>`
const p = Promise.all([promise1, promise2, promise3] as const)
Copy the code
declare function PromiseAll<T extends readonly unknown[] > (
  args: readonly [...T]
) :Promise<{ [P in keyof T]: T[P] extends Promise<infer R> ? R : T[P] }>
Copy the code

See mutable tuples: document PR

Type Lookup

Finds a type in a union based on its attributes. Expect the LookUp < Dog | the Cat, ‘Dog’ > get the Dog, the LookUp < Dog | Cat, ‘Cat’ > get the Cat.

interface Cat {
  type: 'cat'
  breeds: 'Abyssinian' | 'Shorthair' | 'Curl' | 'Bengal'
}

interface Dog {
  type: 'dog'
  breeds: 'Hound' | 'Brittany' | 'Bulldog' | 'Boxer'
  color: 'brown' | 'white' | 'black'
}

type MyDog = LookUp<Cat | Dog, 'dog'> // expected to be `Dog`
Copy the code
type LookUp<U, T> = U extends { type: T } ? U : never
Copy the code

Trim Left

Delete the opening space example:

type trimed = TrimLeft<' Hello World '> // expected to be 'Hello World '
Copy the code
type TWhiteSpace = ' ' | '\n' | '\t'
type TrimLeft<S extends string> = S extends `${TWhiteSpace}${infer R}`
  ? TrimLeft<R>
  : S
Copy the code

Trim

Remove whitespace from both sides

type trimed = Trim<' Hello World '> // expected to be 'Hello World'
Copy the code
type TWhiteSpace = ' ' | '\n' | '\t'
type TrimLeft<S extends string> = S extends `${TWhiteSpace}${infer R}`
  ? TrimLeft<R>
  : S
type TrimRight<S extends string> = S extends `${infer R}${TWhiteSpace}`
  ? TrimRight<R>
  : S

type Trim<T extends string> = TrimRight<TrimLeft<T>>

Copy the code

See more information about template strings and infer:

Infer: www.typescriptlang.org/docs/handbo…

Support for template strings: github.com/microsoft/T…

Capitalize

Capitalize converts the first letter to Capitalize

example:

type capitalized = Capitalize<'hello world'> // expected to be 'Hello world'
Copy the code
type Capitalize<S extends string> = S extends `${infer L}${infer R}`
  ? `${Uppercase<L>}${R}`
  : ' '
Copy the code

Replace

example:

type replaced = Replace<'types are fun! '.'fun'.'awesome'> // expected to be 'types are awesome! '
Copy the code
type Replace<
  S extends string,
  From extends string,
  To extends string
> = From extends ' '
  ? S
  : S extends `${infer L}${From}${infer R}`
  ? `${L}${To}${R}`
  : S
Copy the code

ReplaceAll

example:

type replaced = ReplaceAll<'t y p e s'.' '.' '> // expected to be 'types'
Copy the code
type ReplaceAll<
  S extends string,
  From extends string,
  To extends string
> = From extends ' '
  ? S
  : S extends `${infer L}${From}${infer R}`
  ? `${ReplaceAll<L, From, To>}${To}${ReplaceAll<R, From, To>}`
  : S
Copy the code

Additional parameters

example:

type Fn = (a: number, b: string) = > number

type Result = AppendArgument<Fn, boolean> 
(a: number, b: string, x: Boolean) => number
Copy the code
type AppendArgument<Fn extends(... s:any[]) = >any, A> = Fn extends (
  ...s: infer T
) => infer R
  ? (. s: [...T, A]) = > R
  : never
Copy the code

Permutation

Implements the conversion of a union type to an array type containing a union permutation

example:

type perm = Permutation<'A' | 'B' | 'C'>; // ['A', 'B', 'C'] | ['A', 'C', 'B'] | ['B', 'A', 'C'] | ['B', 'C', 'A'] | ['C', 'A', 'B'] | ['C', 'B', 'A']
Copy the code
// Distribute the first element, then remove the first element, recursively determine the remaining elements
type Permutation<T, P = T> = [T] extends [never]? [] : Textends any
  ? [T, ...Permutation<Exclude<P, T>>]
  : never
Copy the code

Length of String

Calculates the length of the string

type LengthOfString<S extends string, T extends any[] = []> = S extends `${infer L}${infer R}`
  ? LengthOfString<R, [...T, L]>
  : T['length']
Copy the code

Flatten

example:

type flatten = Flatten<[1.2[3.4], [[[5]]]] >// [1, 2, 3, 4, 5]
Copy the code
type Flatten<T extends any[]> = T extends [infer First, ...infer Rest]
  ? [...(First extends any[] ? Flatten<First> : [First]), ...Flatten<Rest>]
  : T
Copy the code

Append to object

Implements a type that adds a new field to the interface

example:

type Test = { id: '1' }
type Result = AppendToObject<Test, 'value'.4> // expected to be { id: '1', value: 4 }
Copy the code
type Merge<T> = {
  [P in keyof T]: T[P]
}

type AppendToObject<T, U extends string, V> = Merge<
  { [P in keyof T]: T[P] } & { [P in U]: V }
>
Copy the code

Take note of this:

Intersection equality problem

type Equal<T, D> = (<S>() = > S extends T ? 1 : 2) extends <S>() = > S extends D
  ? 1
  : 2
  ? true
  : false

type A = {
  name: string
  age: number
}
type B = {
  name: string
}
type Merge<T> = {
  [P in keyof T]: T[P]
}

type IsEqual1 = Equal<A, B & { age: number} >//false
type IsEqual2 = Equal<A, Merge<B & { age: number} > >// true
Copy the code

Absolute

Realize the Absolute. The type that accepts a string, number, or Bigint. The output should be a positive string

example:

type Test = -100;
type Result = Absolute<Test>; // expected to be "100"
Copy the code
type Number = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
type Absolute<T extends number | string | bigint> =
  `${T}` extends `${infer First}${infer Reset}`
    ? `${First}` extends `The ${Number}`
      ? `${First}${Absolute<Reset>}`
      : `${Absolute<Reset>}`
    : ' '
Copy the code

String to Union

example:

type Test = '123';
type Result = StringToUnion<Test>; // expected to be "1" | "2" | "3"
Copy the code
type StringToUnion<T extends string> = T extends `${infer First}${infer Reset}`
  ? First | StringToUnion<Reset>
  : never
Copy the code

Merge

Combine two types into a new type. In the case of the same key, the second overrides the first

type IntersectionToObj<T> = {
  [P in keyof T]:T[P]
}

type Merge<F, S> = IntersectionToObj<Omit<F, keyof S> & S>
Copy the code

CamelCase

example: for-bar-baz -> forBarBaz

type ConcatDash<S extends string> = ` -${S}`
type CamelCase<S extends string> = S extends `${infer L}-${infer M}${infer R}`
  ? M extends The '-'
    ? `${L}-${CamelCase<ConcatDash<R>>}`
    : M extends Uppercase<M>
    ? `${L}-${M}${CamelCase<R>}`
    : `${L}${Uppercase<M>}${CamelCase<R>}`
  : S
Copy the code

KebabCase

example: FooBarBaz -> for-bar-baz

type StringToUnion<T extends string> = T extends `${infer First}${infer Reset}`
  ? First | StringToUnion<Reset>
  : never

type UpperCases = StringToUnion<'ABCDEFGH'> | StringToUnion<'IJKLMNOPQRSTUVXYZ'>
type Split<S extends string> = S extends `${infer L}${infer R}`
  ? L extends UpperCases
    ? L extends Uppercase<L>
      ? ` -${Lowercase<L>}${Split<R>}`
      : `${L}${Split<R>}`
    : `${L}${Split<R>}`
  : S
type DelFirst<T extends string, U extends string> = T extends ` -The ${string}`
  ? U
  : U extends ` -${infer R}`
  ? R
  : U

type KebabCase<T extends string> = DelFirst<T, Split<T>>
Copy the code

Diff

Compare the differences between two object types

type Merge<T> = {
  [P in keyof T]: T[P]
}
type Diff<O, O1> = Merge<
  | {
      [P in Exclude<keyof O, keyof O1>]: O[P]
    } &
      {
        [P in Exclude<keyof O1, keyof O>]: O1[P]
      }
>
Copy the code

AnyOf

Accepts Array, returning true if any element in Array is true. Otherwise return false example:

type Sample1 = AnyOf<[1."".false, [], {}]>; // expected to be true.
type Sample2 = AnyOf<[0."".false, [], {}]>; // expected to be false.
Copy the code
type FalseUnion = false | ' ' | 0 | Record<string | number.never> | []
type AnyOf<T extends readonly any[]> = T extends [infer First, ...infer Reset]
  ? First extends FalseUnion
    ? AnyOf<Reset>
    : true
  : false` ``
Copy the code

It is important to note here that {} is a relatively large set

type A = { name: string } extends{}?true : false // true
type C = { name: string } extends Record<string | number.never>?true : false // false
Copy the code

IsNever

example:

type A = IsNever<never>  // expected to be true
type B = IsNever<undefined> // expected to be false
type C = IsNever<null> // expected to be false
type D = IsNever<[]> // expected to be false
type E = IsNever<number> // expected to be false
Copy the code
type IsNever<T> = [T] extends [never]?true : false
Copy the code

Never is the smallest set, and only never can be assigned to a never document

IsUnion

example:

type case1 = IsUnion<string>  // false
type case2 = IsUnion<string|number>  // true
type case3 = IsUnion<[string|number] >// false
Copy the code

The first way

type IsUnion<T> = (T extends any ? (arg: T) = > void : never) extends (
  arg: infer U
) => void
  ? [T] extends [U]
    ? false
    : true
  : never
Copy the code

Using the inverse property of union type, if it is a union type, the inverse occurs as an intersection type

The second way

type IsUnion<T , U = T> = T extends U? 
  [U] extends [T]?false:true
  :never;
Copy the code

Taking advantage of the property of the distributive, the distributive occurs if T is a union type, where T is one of the subtypes of the union type, and U is T

ReplaceKeys

Implement a type ReplaceKeys that replaces the key in the union type and skips substitution if some type does not have the key. The type has three parameters. example:

type NodeA = {
  type: 'A'
  name: string
  flag: number
}

type NodeB = {
  type: 'B'
  id: number
  flag: number
}

type NodeC = {
  type: 'C'
  name: string
  flag: number
}


type Nodes = NodeA | NodeB | NodeC

type ReplacedNodes = ReplaceKeys<Nodes, 'name' | 'flag', {name: number.flag: string} >// {type: 'A', name: number, flag: string} | {type: 'B', id: number, flag: string} | {type: 'C', name: number, flag: string} // would replace name from string to number, replace flag from number to string.

type ReplacedNotExistKeys = ReplaceKeys<Nodes, 'name', {aa: number} >// {type: 'A', name: never, flag: number} | NodeB | {type: 'C', name: never, flag: number} // would replace name to never
Copy the code
type ReplaceKeys<U, T extends string, Y> = {
  [K in keyof U]: K extends T ? (K extends keyof Y ? Y[K] : never) : U[K]
}
Copy the code

It is important to note here that map types are also distributed when they receive union types

type NodeA = {
  type: 'A'
  name: string
  flag: number
}

type NodeB = {
  type: 'B'
  id: number
  flag: number
}

type NodeC = {
  type: 'C'
  name: string
  flag: number
}

type Nodes = NodeA | NodeB | NodeC
type Map<T> = {
  [P in keyof T]: T[P]
}
// Map<NodeA> | Map<NodeB> | Map<NodeC>
type A = Map<Nodes>
Copy the code

Remove Index Signature

Implement RemoveIndexSignature

to exclude index signatures from object types. Example:

type Foo = {
  [key: string] :any;
  foo(): void;
}
type A = RemoveIndexSignature<Foo>  // expected { foo(): void }
Copy the code
// An index signature parameter type be either 'string' or 'number'. 
type RemoveIndexSignature<T> = {
  [K in keyof T as string extends K
    ? never
    : number extends K
    ? never
    : K]: T[K]
}
Copy the code

Reference: www.typescriptlang.org/docs/handbo…

Percentage parser

example:

type PString1 = ' '
type PString2 = '+ 85%'
type PString3 = '85%'
type PString4 = '85%'
type PString5 = '85'

type R1 = PercentageParser<PString1>  // expected ['', '', '']
type R2 = PercentageParser<PString2>  // expected ["+", "85", "%"]
type R3 = PercentageParser<PString3>  // expected ["-", "85", "%"]
type R4 = PercentageParser<PString4>  // expected ["", "85", "%"]
type R5 = PercentageParser<PString5>  // expected ["", "85", ""]
Copy the code
type Num = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
type Operation = '+' | The '-'
type PercentageParser<T, U extends string[] = [], N = 0> = N extends 0
  ? T extends `${infer L}${infer R}`
    ? L extends Operation
      ? PercentageParser<R, [L], 1>
      : PercentageParser<T, [' '].1>
    : PercentageParser<T, [' '.' '].2>
  : N extends 1
  ? T extends `${infer L}${infer R}`
    ? L extends Num
      ? PercentageParser<R, [U[0].`${U[1] extends string ? U[1] : ' '}${L}`].1>
      : PercentageParser<T, [U[0], U[1] extends string ? U[1] : ' '].2>
    : PercentageParser<T, [U[0], U[1] extends string ? U[1] : ' '].2>
  : N extends 2
  ? T extends `${infer L}`
    ? L extends The '%'
      ? [U[0], U[1].The '%']
      : [U[0], U[1].' ']
    : [U[0], U[1].The '%']
  : never
Copy the code

Drop Char

Removes the specified character from the string.

example:

type Butterfly = DropChar<' b u t t e r f l y ! '.' '> // 'butterfly! '
Copy the code
type EqualType<T, R> = [T] extends [R] ? ([R] extends [T] ? true : false) : false
type DropChar<
  S extends string,
  C extends string
> = S extends `${infer L}${infer R}`
  ? `${EqualType<L, C> extends true ? ' ' : L}${DropChar<R, C>}`
  : ' '

// Display exceeds recursion limit
// type DropChar<S extends string, C> = S extends `${infer L}${infer R}`
/ /? L extends C
/ /? C extends L
/ /? DropChar
      ,>
// : `${L}${DropChar
      
       }`
      ,>
// : `${L}${DropChar
      
       }`
      ,>
/ / :"

Copy the code

MinusOne

Given a number (always positive) as a type. Returns the number reduced by 1. example:

type Zero = MinusOne<1> / / 0
type FiftyFour = MinusOne<55> / / 54
Copy the code
type Make10Array<T extends any[]> = [
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T
]
type Make1Array<T, L extends any> = = [] []`${L['length']}` extends T
  ? L
  : Make1Array<T, [...L, 1] >type AddEqualArrayLength<
  T extends string,
  L extends any[] = []
> = T extends `${infer U}${infer R}`
  ? AddEqualArrayLength<R, [...Make10Array<L>, ...Make1Array<U>]>
  : L

type Pop<T extends any[]> = T extends [...infer F, number]? F :never
type MinusOne<T extends number> = Pop<AddEqualArrayLength<`${T}`> > ['length']
Copy the code

PickByType

example:

type OnlyBoolean = PickByType<{
  name: string
  count: number
  isReadonly: boolean
  isEnable: boolean
}, boolean> // { isReadonly: boolean; isEnable: boolean; }
Copy the code
type EqualType<T, R> = [T] extends [R]
  ? [R] extends [T]
    ? true
    : false
  : false

type PickByType<T, U> = {
  [P in keyof T as EqualType<T[P], U> extends true ? P : never]: T[P]
}
Copy the code

StartsWith

example:

type a = StartsWith<'abc'.'ac'> // expected to be false
type b = StartsWith<'abc'.'ab'> // expected to be true
type c = StartsWith<'abc'.'abcd'> // expected to be false
Copy the code
type StartsWith<T extends string, U extends string> = T extends `${U}The ${string}` ? true : false
Copy the code

EndsWith

Implement EndsWith<T, U>, which takes two exact string types and returns whether T ends in U

type EndsWith<T extends string, U extends string> = T extends `The ${string}${U}` ? true : false
Copy the code

PartialByKeys

Specifies that keys in the object type is optional

example:

interface User {
  name: string
  age: number
  address: string
}

type UserPartialName = PartialByKeys<User, 'name'> // { name? :string; age:number; address:string }
Copy the code
type IntersectionToObj<T> = {
  [K in keyof T]: T[K]
}
type PartialByKeys<T , K = any> = IntersectionToObj<{
  [P in keyof T as P extends K ? P : never]? : T[P] } & { [Pin Exclude<keyof T, K>]: T[P]
}>
Copy the code

RequiredByKeys

Implement a generic RequiredByKeys

that takes two type parameters T and K. K specifies the set of attributes that should be set to the required T. When K is not provided, it should require all attributes as normal Required

.

,>

example:

interfaceUser { name? :stringage? :numberaddress? :string
}

type UserPartialName = RequiredByKeys<User, 'name'> // { name: string; age? : number; address? : string }
Copy the code
type IntersectionToObj<T> = {
  [K in keyof T]: T[K]
}
type RequiredByKeys<T , K = any> = IntersectionToObj<{
  [P in keyof T as P extends K ? P : never] -? : T[P] } & { [PinExclude<keyof T, K>]? : T[P] }>Copy the code

Mutable

Implement generic Mutable

, which makes all properties in T Mutable (not read-only).

example:

interface Todo {
  readonly title: string
  readonly description: string
  readonly completed: boolean
}

type MutableTodo = Mutable<T> // { title: string; description: string; completed: boolean; }
Copy the code
type Mutable<T> = {
  -readonly [K in keyof T]:T[K]
}
Copy the code

difficult

Simple Vue type

Implement a simplified version of vUe-like type support.

By providing the function name SimpleVue (similar to vue.extend or defineComponent), it should correctly infer the this type inside the calculation and method.

In this challenge, we assume that SimpleVue accepts Object with data, computed, and methods fields as its only parameters,

– Data is a simple function that returns an object that exposes the context this, but you cannot access the data itself or other computer values or methods in data.

– Computed is taking the context as the object of this’s function, doing some computation and returning the result. The computed results should be presented to the context as simple return values rather than functions.

-methods are objects of functions and have the same context as this. Methods can access fields exposed by Data, computed, and other methods. Computed differs from methods in the functionality exposed as-is.

SimpleVue’s return value type can be arbitrary.

// The derivation here is magic
type A = Record<string.any>

interface Options<D, C extends A, M> {
  data: (this: void) = > D
  computed: C & ThisType<D & C>
  methods: M & ThisType<M & { [P in keyof C]: ReturnType<C[P]> }>
}
declare function SimpleVue<D.C extends A.M> (options: Options<D, C, M>) :any
Copy the code
// Another intuitive way to write it
type ComputedReturnType<T> = T extends Record<string.() = > any>? { [Pin keyof T]: ReturnType<T[P]>
    }
  : never

interface Options<D, C, M> {
  data: (this: void) = > D
  computed: C & ThisType<D>
  methods: M & ThisType<M & ComputedReturnType<C>>
}
declare function SimpleVue<D.C.M> (options: Options<D, C, M>) :any
Copy the code

Reference: function generic ThisType

Cory, 1

example:

const add = (a: number, b: number) = > a + b
const three = add(1.2)

const curriedAdd = Currying(add)
const five = curriedAdd(2) (3)
Copy the code
type Curr<A extends any[], R> = A extends [...infer F, infer L]
  ? Curr<F, (x: L) = > R>
  : R

declare function Currying<T> (
  fn: T
) :T extends (. argv: infer A) = >infer R ? Curr<A.R> : never
Copy the code

Union to Intersection

example:

type I = Union2Intersection<'foo' | 42 | true> // expected to be 'foo' & 42 & true
Copy the code
type UnionToIntersection<U> = (U extends any ? (x: U) = > void : never) extends (
  x: infer R
) => void
  ? R
  : never
Copy the code

Get Required

Implements the advanced Util type GetRequired

, which preserves all the required field Example:

type I = GetRequired<{ foo: number, bar? :string} >// expected to be { foo: number }
Copy the code
type IfEquals<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false
type GetRequired<T> = {
  [P in keyof T as IfEquals<
    { [O in P]: T[P] },
    { [O inP]-? : T[P] } >extends true
    ? P
    : never]: T[P]
}
Copy the code
/ / based on
typeF = { name? :string
}
type R = {
  name: string
}

[] is added to prevent union types from assigning patterns
type IfEquals<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false
type G = IfEquals<F, R>    // false

Copy the code

Get Optional

Implements the high-level util type GetOptional

, which preserves all optional fields

type I = GetOptional<{ foo: number, bar? :string} >// expected to be { bar? : string }
Copy the code
// one 
type IfEquals<X, Y> = [X] extends [Y] ? ([Y] extends [X] ? true : false) : false
type GetOptional<T> = {
  [P in keyof T as IfEquals<
    { [O in P]: T[P] },
    { [O inP]-? : T[P] } >extends true
    ? never: P]? : T[P] }Copy the code
// two
type FilterOptionalKey<T, K extends keyof T> = T extends {
  [k inK]-? : T[k] } ?never
  : K

type GetOptional<T> = {
  [K in keyof T as FilterOptionalKey<T, K>]: T[K]
}
Copy the code

The second approach is based on the following ideas

typehasA = { name? :string
  age: number
}
type A = {
  name: string
  age: number
}
type B = {
  name: string
}

type C = hasA extends B ? true : false //false
type D = A extends B ? true : false //true
Copy the code

Is a larger collection when the property is optional

typehasA = { name? :string
  age: number
}
type A = {
  name: string
  age: number
}

type C = hasA extends A ? true : false //false
type D = A extends hasA ? true : false //true
Copy the code

Required Keys

Implement the high-level util type RequiredKeys

, which selects all RequiredKeys as a union. example:

type Result = RequiredKeys<{ foo: number; bar? :string} >.// Expected to be "foo"
Copy the code
type RequiredKeys<T> = keyof {
  [P in keyof T as T[P] extends Required<T>[P] ? P : never] : T[P]
}
Copy the code

Optional Keys

Implements the high-level util type OptionalKeys

, which merges all OptionalKeys into a union.

// one
type OptionalKeys<T, K = keyof T> = K extends keyof T
  ? T extends Required<Pick<T, K>>
    ? never
    : K
  : never
Copy the code

Using the allocation mode, check whether T’s keys are optional one by one

//two 
type DropUndefined<T> = T extends undefined ? never : T
type OptionalKeys<T> = DropUndefined<
  {
    [P in keyof T]: {} extends Pick<T, P> ? P : never
  }[keyof T]
>
Copy the code

Note here that optional attributes have a union of undefined after mapping

type Map<T> = {
  [P in keyof T]: T[P]
}
/* type C = { a: number; b? : string | undefined; } * /
type C = MapThe < {a: number; b? :string} >Copy the code

Capitalize Words

Implement CapitalizeWords

, which converts the first letter of each word in the string to uppercase and leaves the rest as is. example:

type capitalized = CapitalizeWords<'hello world, my friends'> // expected to be 'Hello World, My Friends'
Copy the code
type FirstUppercase<T> = T extends `${infer L}${infer R}`
  ? `${Uppercase<L>}${R}`
  : T
type SpiltCode = ' ' | '. ' | ', '

type Recursion<S extends string> = S extends `${infer M}${infer L}${infer R}`
  ? M extends SpiltCode
    ? `${M}${Uppercase<L>}${Recursion<R>}`
    : `${M}${Recursion<`${L}${R}`>}`
  : S

type CapitalizeWords<T extends string> = Recursion<FirstUppercase<T>>
Copy the code

CamelCase

Implement CamelCase

to convert snake_case string to CamelCase.

example:

type camelCase1 = CamelCase<'hello_world_with_types'> // expected to be 'helloWorldWithTypes'
type camelCase2 = CamelCase<'HELLO_WORLD_WITH_TYPES'> // expected to be same as previous one
Copy the code
type CamelCase<S extends string> = S extends `${infer L}_${infer R}${infer I}`
  ? `${Lowercase<L>}${Uppercase<R>}${CamelCase<I>}`
  : Lowercase<S>
Copy the code

C-printf Parser

C has a function called printf. This function allows us to print content with formatting. Like this,

printf("The result is %d.", 42);
Copy the code

This problem requires you to parse the input string and extract format placeholders, such as %d and %f. For example, if the input string is “The result is %d.”, the parse result is a tuple [‘dec’].

This is the mapping

type ControlsMap = {
  c: 'char'.s: 'string'.d: 'dec'.o: 'oct'.h: 'hex'.f: 'float'.p: 'pointer',}Copy the code
type ParsePrintFormat<
  S extends string,
  T extends any[] = []
> = S extends `The ${string}%${infer M}${infer R}`
  ? M extends keyof ControlsMap
    ? ParsePrintFormat<R, [...T, ControlsMap[M]]>
    : ParsePrintFormat<R, [...T]>
  : T
Copy the code

Vue Basic Props

The challenge starts with Simple Vue, and you should complete that first, then modify your code based on it to start the challenge.

type Constructor<T> = T extends(... args:any) => infer R
  ? R
  : T extends new(... args:any) => infer R2
  ? R2
  : any

type PropType<T> = T extends Array<any>? Constructor<T[number]>
  : Constructor<T>

type Prop<P> = {
  [K in keyof P]: P[K] extends{}?'type' extends keyof P[K]
      ? PropType<P[K]['type']>
      : PropType<P[K]>
    : PropType<P[K]>
}

type ComputedReturnType<T> = T extends Record<string.() = > any>? { [Pin keyof T]: ReturnType<T[P]>
    }
  : never

interface Options<P, D, C, M> {
  props: P
  data: (this: Prop<P>) = > D
  computed: C & ThisType<D>
  methods: M & ThisType<M & ComputedReturnType<C> & Prop<P>>
}
declare function VueBasicProps<P.D.C.M> (options: Options<P, D, C, M>) :any
Copy the code

The important thing to note is that when class is used directly, TS is automatically inferred as typeof class

class ClassA {}

function test<T> (s: T) {
  return s
}
// const r: typeof ClassA
const r = test(ClassA)
Copy the code

Combine that with this conclusion

/** * defines a class */
class People {
  name: number;
  age: number;
  constructor(){}}// p1 can be assigned normally
const p1: People = new People();
Type "typeof People" lacks the following attributes of type "People" : name, age
const p2: People = People;

Type "People" is missing attribute "prototype", but type" typeof People" needs attribute
const p3: typeof People = new People();
// p4 can be assigned normally
const p4: typeof People = People;
Copy the code
  • When a class is treated directly as a type, the constraint for that type is that it must be an instance of the class
  • When theTypeof classAs a type, the constraint satisfies the type of the class

So it’s going to be

T extends new(... args:any) => infer R2
Copy the code

IsAny

Write a utility type IsAny

that accepts input type T. Return true if T is any, false otherwise

// one
type IsAny<T> =  unknown extends T
    ? [T] extends [string]?true
      : false
    : false
Copy the code
  1. unkonwCan only be assigned tounkonworany
  2. anyExcept you can assign tounkonwYou can also assign values other than never

Reference: www.typescriptlang.org/docs/handbo…

// two
ref: https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
type IsAny<T> = 0 extends (1 & T) ? true : false;
Copy the code

Any is the parent and subclass of all types (except never), so when 1 & any is any

Typed Get

Implementation version TS, similar to loDash get functionality in this challenge does not require access to arrays. example:

type Data = {
  foo: {
    bar: {
      value: 'foobar'.count: 6,},included: true,},hello: 'world'
}
  
type A = Get<Data, 'hello'> // 'world'
type B = Get<Data, 'foo.bar.count'> / / 6
type C = Get<Data, 'foo.bar'> // { value: 'foobar', count: 6 }
Copy the code
type Get<T, K> = K extends `${infer L}.${infer R}`
  ? L extends keyof T
    ? Get<T[L], R>
    : never
  : K extends keyof T
  ? T[K]
  : never
Copy the code

String to Number

Converts string literals to numbers

type Map = {
  '0': []
  '1': [1]
  '2': [...Map['1'].1]
  '3': [...Map['2'].1]
  '4': [...Map['3'].1]
  '5': [...Map['4'].1]
  '6': [...Map['5'].1]
  '7': [...Map['6'].1]
  '8': [...Map['7'].1]
  '9': [...Map['8'].1]}type Make10Array<T extends any[]> = [
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T,
  ...T
]
type ToNumber<
  S extends string,
  L extends any[] = []
> = S extends `${infer F}${infer R}`
  ? ToNumber<R, [...Make10Array<L>, ...(F extends keyof Map ? Map[F] : never)]>
  : L['length']
Copy the code

Tuple Filter

Implement a type FilterOut<T, F> that filters out the given type F from the tuple T

example:

type Filtered = FilterOut<[1.2.null.3].null> / / [1, 2, 3]
Copy the code
type FilterOut<T extends any[], F, Stash extends any[] = []> = T extends [
  infer L,
  ...infer R
]
  ? [L] extends [F]
    ? FilterOut<R, F, Stash>
    : FilterOut<R, F, [...Stash, L]>
  : Stash
Copy the code

Tuple to Enum Object

example:

Enum<["macOS"."Windows"."Linux"] >// -> { readonly MacOS: "macOS", readonly Windows: "Windows", readonly Linux: "Linux" }

Enum<["macOS"."Windows"."Linux"].true>
// -> { readonly MacOS: 0, readonly Windows: 1, readonly Linux: 2 }
Copy the code
type IndexOf<T, P, S extends any[] = []> = T extends readonly [
  infer F,
  ...infer R
]
  ? P extends F
    ? S['length']
    : IndexOf<R, P, [...S, 1]>
  : S['length']

type Enum<T extends readonly string[], N extends boolean = false> = {
  readonly [P in T[number] as Capitalize<P>]: N extends true ? IndexOf<T, P> : P
}
Copy the code

printf

example:

type FormatCase1 = Format<"%sabc"> // FormatCase1 : string => string
type FormatCase2 = Format<"%s%dabc"> // FormatCase2 : string => number => string
type FormatCase3 = Format<"sdabc"> // FormatCase3 : string
type FormatCase4 = Format<"sd%abc"> // FormatCase4 : string
Copy the code
// Can be extended
type MapDict = {
  s: string
  d: number
}

type Format<T extends string> = T extends `The ${string}%${infer M}${infer R}`
  ? M extends keyof MapDict
    ? (x: MapDict[M]) = > Format<R>
    : Format<R>
  : string
Copy the code

Deep object to unique

Creates a type that takes an object and makes it and all deeply nested objects in it unique, preserving the string and number keys of all objects, as well as the values of all attributes on those keys.

example:

import { Equal } from "@type-challenges/utils"

type Foo = { foo: 2; bar: { 0: 1 }; baz: { 0: 1}}type UniqFoo = DeepObjectToUniq<Foo>

declare let foo: Foo
declare let uniqFoo: UniqFoo

uniqFoo = foo // ok
foo = uniqFoo // ok

type T0 = Equal<UniqFoo, Foo> // false
type T1 = UniqFoo["foo"] / / 2
type T2 = Equal<UniqFoo["bar"], UniqFoo["baz"] >// false
type T3 = UniqFoo["bar"] [0] / / 1
type T4 = Equal<keyof Foo & string, keyof UniqFoo & string> // true
Copy the code
type Merge<T> = {
  [P in keyof T]: T[P]
}
type DeepObjectToUniq<O extends object> = {
  [P in keyof O]: O[P] extends object? DeepObjectToUniq<Merge<O[P] & { _xxx? : [O, P] }>> : O[P] }Copy the code

Length of String 2

Implement a type LengthOfString to calculate the length of a template string. This type must support strings several hundred characters long. (In general, recursive calculation of string length is limited by the depth of recursive function calls in TS, that is, it supports strings up to 45 characters long.)

example:

type T0 = LengthOfString<"foo"> / / 3
Copy the code
type LengthOfString<S extends string, T extends any[] = []> = S extends ' '
  ? T['length']
  : S extends `${infer L}${infer Q}${infer W}${infer E}${infer Y}${infer U}${infer I}${infer O}${infer P}${infer R}`
  ? LengthOfString<R, [...T, L, Q, W, E, Y, U, I, O, P]>
  : S extends `${infer L}${infer R}`
  ? LengthOfString<R, [...T, L]>
  : T['length']
Copy the code

And the idea here is very simple, let’s do more matches per recursion,

Union to Tuple

Implement a type, UnionToTuple, that converts the union to a tuple.

Because union types are unordered, but tuples are ordered, it is ok to output elements in any order in this challenge

example:

UnionToTuple<1>           // [1], and correct
UnionToTuple<'any' | 'a'> // ['any','a'], and correct
Copy the code

It should not be a tuple union

UnionToTuple<'any' | 'a'> // ['a','any'], and correct
Copy the code

Associations can break down, meaning that some types can absorb (or be absorbed by others) and cannot prevent such absorption. See the following example:

Equal<UnionToTuple<any | 'a'>,       UnionToTuple<any>>         // will always be a true
Equal<UnionToTuple<unknown | 'a'>,   UnionToTuple<unknown>>     // will always be a true
Equal<UnionToTuple<never | 'a'>,     UnionToTuple<'a'>>         // will always be a true
Equal<UnionToTuple<'a' | 'a' | 'a'>, UnionToTuple<'a'>>         // will always be a true
Copy the code

answers:

// https://github.com/type-challenges/type-challenges/issues/737
type UnionToIntersection<T> = (T extends any ? (x: T) = > any : never) extends (
  x: infer U
) => any
  ? U
  : never

// get last Union: LastUnion<1|2> => 2
// ((x: A) => any) & ((x: B) => any) is overloaded function then Conditional types are inferred only from the last overload
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#type-inference-in-conditional-types
type LastUnion<T> = UnionToIntersection<
  T extends any ? (x: T) = > any : never
> extends (x: infer L) => any
  ? L
  : never

type UnionToTuple<T, Last = LastUnion<T>> = [T] extends [never]? [] : [...UnionToTuple<Exclude<T, Last>>, Last]Copy the code

What’s harder to understand is LastUnion

  1. Here we first convert each element of the union type to a function

    (x: T) => any | (x: T) => any | (x: T) => any

  2. Using the allocation pattern and the contravariance of the function, the union type is converted to the intersection type

    (x: T) => any & (x: T) => any & (x: T) => any

  3. When conditional inference is used with multi-signature types (such as function overloading), only the last signature is inferred

    Reference: www.typescriptlang.org/docs/handbo…

String Join

Create a string concatenation utility example:

const hyphenJoiner = join(The '-')
const result = hyphenJoiner('a'.'b'.'c'); // = 'a-b-c'

/ / or
join(The '#') ('a'.'b'.'c') // = 'a#b#c'

// When we pass an empty delimiter (i.e. ") to concatenate, the strings should be concatenated as is, i.e
join(' ') ('a'.'b'.'c') // = 'abc'

// When only one item is passed, the original item should be returned (without adding any delimiters)
join(The '-') ('a') // = 'a'
Copy the code
type JoinStr<
  J extends string,
  T extends string[],
  S extends string = ' '
> = T extends [infer F, ...infer R]
  ? F extends string
    ? R extends string[]? Sextends ' '
        ? JoinStr<J, R, `${F}`>
        : JoinStr<J, R, `${S}${J}${F}`>
      : `${S}${J}${F}`
    : S
  : S
declare function join<J extends string> (
  delimiter: J
).T extends string[] > (. parts: T) = >JoinStr<J.T>
Copy the code

Deepick

Implement a type DeepPick that extends the utility type Pick

exapmle:


type obj = {
  name: 'hoge'.age: 20.friend: {
    name: 'fuga'.age: 30.family: {
      name: 'baz'.age: 1}}}type T1 = DeepPick<obj, 'name'>   // { name : 'hoge' }
type T2 = DeepPick<obj, 'name' | 'friend.name'>  // { name : 'hoge' } & { friend: { name: 'fuga' }}
type T3 = DeepPick<obj, 'name' | 'friend.name' |  'friend.family.name'>  // { name : 'hoge' } & { friend: { name: 'fuga' }} & { friend: { family: { name: 'baz' }}}
Copy the code
type UnionToIntersection<T> = (T extends any ? (x: T) = > any : never) extends (
  x: infer R
) => any
  ? R
  : never

type GetValue<T, K extends string> = K extends keyof T
  ? Pick<T, K>
  : K extends `${infer L}.${infer R}`
  ? {
      [P in L]: P extends keyof T ? GetValue<T[P], R> : unknown
    }
  : unknown
type DeepPick<T, K extends string> = UnionToIntersection<GetValue<T, K>>
Copy the code

Pinia

Create a type-level function whose type is similar to that of the Pinia library. You don’t actually need to implement the function, just add the type.

This function only accepts arguments of one object type. This object contains four properties

  • id– Just a string (required)
  • state– A function that returns an object as a store state (required)
  • getters– An object with calculated values similar to Vue or getter methods for Vuex, details as follows (optional)
  • actions– An object with methods that can perform side effects and change state, detailed as follows (optional)

Getters

When you define a store like this

const store = defineStore({
  / /... other required fields
  getters: {
    getSomething() {
      return 'xxx'}}})Copy the code

You can use it like this

store.getSomething
Copy the code

It can’t be used like this

store.getSomething()  // error
Copy the code

In addition, getters can access state and/or other getters through this, but state is read-only.

Actions

When you define a store like this

const store = defineStore({
  / /... other required fields
  actions: {
    doSideEffect() {
      this.xxx = 'xxx'
      return 'ok'}}})Copy the code

You can call it directly

const returnValue = store.doSideEffect()
Copy the code

Actions can return any value or none, and it can accept any number of different types of arguments. Parameter types and return types cannot be lost, which means type checking must be available on the calling side.

You can get and change state through this, and you can get getters through this but it’s read-only

` `

type GetGetters<T> = {
  [P in keyof T]: T[P] extends(... arg:any[]) => infer R ? R : never
}
type OptionsType<S, G, A> = {
  id: string
  state: () = > Readonly<S>
  getters: G & ThisType<GetGetters<G> & Readonly<S>>
  actions: A & ThisType<A & S>
}
declare function defineStore<S.G.A> (
  store: OptionsType<S, G, A>
) :A & Readonly<S> & GetGetters<G>
Copy the code

The vUE challenge is the same, using the inference of function generics

Camelize

Implement Camelize to convert an object from snake_case to camelCase

example:

Camelize<{
  some_prop: string.prop: { another_prop: string },
  array: [{ snake_case: string }]
}>

// expected to be
/ / {
// someProp: string,
// prop: { anotherProp: string },
// array: [{ snakeCase: string }]
// }
Copy the code
type TransformCamelcase<T> = T extends `${infer L}_${infer R}`
  ? `${L}${Capitalize<R>}`
  : T

type CamelikeTuple<T> = T extends [infer F, ...infer R]
  ? [Camelize<F>, ...CamelikeTuple<R>]
  : []

type CamelikeObject<T> = {
  [P in keyof T as TransformCamelcase<P>]: Camelize<T[P]>
}

type Camelize<T> = T extends Array<any>? CamelikeTuple<T> : Textends{}? CamelikeObject<T> : TCopy the code

Drop String

Removes the specified character from the string.

example:

type Butterfly = DropString<'foobar! '.'fb'> // 'ooar! '
Copy the code
type StringToUnion<S extends string> = S extends `${infer F}${infer Rest}`
  ? F | StringToUnion<Rest>
  : never

type DropStringUnion<
  S extends string,
  R
> = S extends `${infer A}${infer B}${infer Rest}`
  ? A extends R
    ? B extends R
      ? `${DropStringUnion<Rest, R>}`
      : `${B}${DropStringUnion<Rest, R>}`
    : B extends R
    ? `${A}${DropStringUnion<Rest, R>}`
    : `${A}${B}${DropStringUnion<Rest, R>}`
  : S extends `${infer A}${infer Rest}`
  ? A extends R
    ? `${DropStringUnion<Rest, R>}`
    : `${A}${DropStringUnion<Rest, R>}`
  : S

type DropString<S extends string, R extends string> = DropStringUnion<
  S,
  StringToUnion<R>
>
Copy the code

Split

example:

type result = Split<'Hi! How are you? '.' '>  // should be ['Hi!', 'How', 'are', 'you?']
Copy the code
type Split<
  S extends string,
  SEP extends string
> = S extends `${infer L}${SEP}${infer R}`
  ? R extends ' '
    ? [L]
    : [L, ...Split<R, SEP>]
  : S extends ' '
  ? SEP extends ' '
    ? []
    : [S]
  : S extends `${infer R}`
  ? [S]
  : string[]
Copy the code

hell

Get read-only fields

Implements the generic GetReadonlyKeys

that returns the union of the read-only keys of an object.

example:

interface Todo {
  readonly title: string
  readonly description: string
  completed: boolean
}

type Keys = GetReadonlyKeys<Todo> // expected to be "title" | "description"
Copy the code
type IsEqual<X, Y, A = true, B = false> = (<T>() = > T extends X
  ? 1
  : 2) extends <T>() = > T extends Y ? 1 : 2
  ? A
  : B
type GetReadonlyKeys<T> = {
  [P inkeyof T]-? : IsEqual< { [Oin P]: T[P] },
    { -readonly [O in P]: T[P] },
    never,
    P
  >
}[keyof T]
Copy the code

The rest of the hell level of individuals think it involves more algorithms, so they didn’t continue to do it, interested students can play