In 2020, Po ge wrote a series of articles entitled “Relearning TS 1.0”, with a total of more than 50 articles. TS is developing rapidly, more and more projects are using TS to develop, the current VERSION of TS has been updated to version 4.4. Recently, Po Ge trained TS topic in the team, and found that some partners did not master TS very well, and did not have a clear understanding of the related concepts of TS type programming (type gymnastics). As a result, He has decided to update TS 2.0 Relearn, which will complement and update the previous article and introduce new features for TS 4.0 and beyond. We will also highlight some of the core concepts in TS, such as type safety, structural type system, mapping type, generics, contravariant/covariant, etc.

Before the series begins, let’s do some warm-up exercises. If you are interested, you can try them. Partial reference answers to these 20 questions have been submitted to this warehouse. Consider using TS Playground online, using the latest version of the compiler: V4.4.2.

The first question

type User = {
  id: number;
  kind: string;
};

function makeCustomer<T extends User> (u: T) :T {
  // Error (TS compiler: v4.4.2)
  // Type '{ id: number; kind: string; }' is not assignable to type 'T'.
  // '{ id: number; kind: string; }' is assignable to the constraint of type 'T', 
  // but 'T' could be instantiated with a different subtype of constraint 'User'.
  return {
    id: u.id,
    kind: 'customer'}}Copy the code

Why does the above code display errors, and how can I resolve the above problems?

The second question

In this example, we want parameters A and B to be of the same type, i.e. both a and B are of type number or string. When their types are inconsistent, the TS type checker automatically prompts the corresponding error message.

function f(a: string | number, b: string | number) {
  if (typeof a === 'string') {
    return a + ':' + b; // no error but b can be number!
  } else {
    return a + b; // error as b can be number | string
  }
}

f(2.3); // Ok
f(1.'a'); // Error
f('a'.2); // Error
f('a'.'b') // Ok
Copy the code

The third question

Partial

Sets Partial

to optional. Partial

sets Partial

to optional. .



interface Todo {
  title: string;
  description: string;
}

function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {
  return{... todo, ... fieldsToUpdate }; }// lib.es5.d.ts
type Partial<T> = {
  [P inkeyof T]? : T[P]; };Copy the code

So how do you define a SetOptional tool type that supports optional properties for a given key? The following is an example:

type Foo = {
	a: number; b? :string;
	c: boolean;
}

// Test case
type SomeOptional = SetOptional<Foo, 'a' | 'b'>;

// type SomeOptional = {
// a? : number; // This property has been made optional
// b? : string; // Keep the same
// c: boolean;
// }
Copy the code

After implementing the SetOptional tool type, you can continue to implement the SetRequired tool type if you are interested in it for the guidance of the specified Keys to become mandatory. The following is an example:

typeFoo = { a? :number;
	b: string; c? :boolean;
}

// Test case
type SomeRequired = SetRequired<Foo, 'b' | 'c'>;
// type SomeRequired = {
// 	a?: number;
// b: string; // Keep the same
// c: boolean; // This property has become required
// }
Copy the code

The fourth question

The effect of Pick

is to Pick out the subproperties of a type into subtypes that contain some of the properties of that type.
,>

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

type TodoPreview = Pick<Todo, "title" | "completed">;

const todo: TodoPreview = {
  title: "Clean room".completed: false
};
Copy the code

So how do you define a ConditionalPick tool type, support, according to the specified Condition Condition to generate new type corresponding to the use of the sample is as follows:

interface Example {
	a: string;
	b: string | number;
	c: () = > void;
	d: {};
}

// Test case:
type StringKeysOnly = ConditionalPick<Example, string>;
//=> {a: string}
Copy the code

The fifth problem

AppendArgument adds an argument of the specified type to the existing function type. The new argument is named x, which will be the first argument of the new function type. The specific usage example is as follows:

type Fn = (a: number, b: string) = > number
type AppendArgument<F, A> = // Your implementation code

type FinalFn = AppendArgument<Fn, boolean> 
// (x: boolean, a: number, b: string) => number
Copy the code

The sixth question

Define a NativeFlat tool type that supports flat (flattening) of array types. The specific usage example is as follows:

type NaiveFlat<T extends any[] > =// Your implementation code

// Test case:
type NaiveResult = NaiveFlat<[['a'], ['b'.'c'], ['d']] >/ / NaiveResult results: "a" | "b" | | "c" "d"
Copy the code

After completing the NaiveFlat utility type, we continue to implement the DeepFlat utility type to support multi-dimensional array types:

type DeepFlat<T extends any[]> = unknown // Your implementation code

// Test case
type Deep = [['a'], ['b'.'c'], [['d']], [[[['e']]]]];
type DeepTestResult = DeepFlat<Deep>  
// DeepTestResult: "a" | "b" | "c" | "d" | "e"
Copy the code

Number 7

Define an EmptyObject type using a type alias so that only EmptyObject assignments are allowed:

type EmptyObject = {} 

// Test case
const shouldPass: EmptyObject = {}; // It can be assigned normally
const shouldFail: EmptyObject = { // A compilation error will occur
  prop: "TS"
}
Copy the code

After testing with the EmptyObject type test case, let’s change the type definition of the following takeSomeTypeOnly function so that its parameters only allow values strictly of type SomeType. The specific usage example is as follows:

type SomeType =  {
  prop: string
}

// Change the type definition of the following function so that its arguments only allow strictly values of type SomeType
function takeSomeTypeOnly(x: SomeType) { return x }

// Test case:
const x = { prop: 'a' };
takeSomeTypeOnly(x) // Can be called normally

const y = { prop: 'a'.addditionalProp: 'x' };
takeSomeTypeOnly(y) // A compilation error will occur
Copy the code

The eighth problem

Defines the NonEmptyArray utility type used to ensure that data is not an empty array.

type NonEmptyArray<T> = // Your implementation code

const a: NonEmptyArray<string> = [] // A compilation error will occur
const b: NonEmptyArray<string> = ['Hello TS'] // Non-empty data, normal use
Copy the code

Tips: There are many ways to solve this problem, and you can try it yourself.

Question 9

Defines a JoinStrArray utility type that concatenates string array types using the specified Separator Separator. The specific usage example is as follows:

type JoinStrArray<Arr extends string[], Separator extends string, Result extends string = ""> = // Your implementation code

// Test case
type Names = ["Sem"."Lolo"."Kaquko"]
type NamesComma = JoinStrArray<Names, ","> // "Sem,Lolo,Kaquko"
type NamesSpace = JoinStrArray<Names, ""> // "Sem Lolo Kaquko"
type NamesStars = JoinStrArray<Names, "⭐ ️"> / / "Sem ⭐ ️ Lolo ⭐ ️ Kaquko"
Copy the code

The first ten questions

Implements a Trim utility type used to whitespace string literal types. The specific usage example is as follows:

type Trim<V extends string> = // Your implementation code

// Test case
Trim<' semlinker '>
//=> 'semlinker'
Copy the code

Tip: Consider defining the TrimLeft and TrimRight tool types and then combining them into Trim tool types.

The eleventh problem

Implements an IsEqual utility type that compares whether two types are equal. The specific usage example is as follows:

type IsEqual<A, B> = // Your implementation code

// Test case
type E0 = IsEqual<1.2>; // false
type E1 = IsEqual<{ a: 1 }, { a: 1} >// true
type E2 = IsEqual<[1], [] >;// false
Copy the code

Question 12

Implements a Head utility type that gets the first type of an array type. The specific usage example is as follows:

type Head<T extends Array<any> > =// Your implementation code

// Test case
type H0 = Head<[]> // never
type H1 = Head<[1] >/ / 1
type H2 = Head<[3.2] >/ / 3
Copy the code

Tips: There are many ways to solve this problem, and you can try it yourself.

Article 13 questions

Implement a Tail utility type that gets the rest of the array types except the first type. The specific usage example is as follows:

type Tail<T extends Array<any> > =// Your implementation code

// Test case
type T0 = Tail<[]> / / []
type T1 = Tail<[1.2] >/ / [2]
type T2 = Tail<[1.2.3.4.5] >// [2, 3, 4, 5]
Copy the code

Tips: There are many ways to solve this problem, and you can try it yourself.

Question 14

Implements an Unshift utility type that adds the specified type E as the first element to the T array type. The specific usage example is as follows:

type Unshift<T extends any[], E> =  // Your implementation code

// Test case
type Arr0 = Unshift<[], 1>; / / [1]
type Arr1 = Unshift<[1.2.3].0>; // [0, 1, 2, 3]
Copy the code

Tips: There are many ways to solve this problem, and you can try it yourself.

Question 15

Implements a Shift utility type that removes the first type in a T-array type. The specific usage example is as follows:

type Shift<T extends any[] > =// Your implementation code

// Test case
type S0 = Shift<[1.2.3] >/ / [2, 3]
type S1 = Shift<[string.number.boolean] >// [number,boolean]
Copy the code

Question 16

Implement a Push utility type that adds the specified type E as the last element to the T array type. The specific usage example is as follows:

type Push<T extends any[], V> = // Your implementation code

// Test case
type Arr0 = Push<[], 1> / / [1]
type Arr1 = Push<[1.2.3].4> // [1, 2, 3, 4]
Copy the code

The 17th title

Implements an Includes tool type that determines whether the specified type E is included in the T array type. The specific usage example is as follows:

type Includes<T extends Array<any>, E> = // Your implementation code

type I0 = Includes<[], 1> // false
type I1 = Includes<[2.2.3.1].2> // true
type I2 = Includes<[2.3.3.1].1> // true
Copy the code

Tips: There are many ways to solve this problem, and you can try it yourself.

18 the topic

Implements a UnionToIntersection tool type that converts a union type to an intersection type. The specific usage example is as follows:

type UnionToIntersection<U> = // Your implementation code

// Test case
type U0 = UnionToIntersection<string | number> // never
type U1 = UnionToIntersection<{ name: string } | { age: number} >// { name: string; } & { age: number; }
Copy the code

Question 19

Implements an OptionalKeys utility type that gets optional properties declared in object types. The specific usage example is as follows:

type Person = {
  id: string;
  name: string;
  age: number;
  from? :string; speak? :string;
};

type OptionalKeys<T> = // Your implementation code
type PersonOptionalKeys = OptionalKeys<Person> // "from" | "speak"
Copy the code

Tips: There are many ways to solve this problem, and you can try it yourself.

Question 20

Implements a Curry utility type that implements the Curryization of function types. The specific usage example is as follows:

type Curry<
  F extends(... args:any[]) = >any,
  P extends any[] = Parameters<F>, 
  R = ReturnType<F> 
> = // Your implementation code

type F0 = Curry<() = > Date>; // () => Date
type F1 = Curry<(a: number) = > Date>; // (arg: number) => Date
type F2 = Curry<(a: number, b: string) = > Date>; // (arg_0: number) => (b: string) => Date
Copy the code

If you can answer all of the above questions easily, your TS level should be good. Of course, if you’re still feeling the need, you can continue with the online TS exercises at 👉 typescript-exercises.github. IO /. If you have good TS exercises, you can recommend to a bao Ge ha.

Pay attention to “the road of full stack Repair fairy” read 4 free e-books of Po Ge original (total downloads of 50,000 +) and 11 advanced series of Vue 3 tutorials.

Relearning TS 2.0 is starting again. If you are interested, you can add Semlinker on wechat and send TS to invite you to join us. Want to know the detailed answer of the small partner, also can private chat Po elder brother. Finally, I wish everyone a happy Mid-Autumn Festival in advance.