14
头图

首图.gif
Contact us : Youdao technical team assistant : ydtech01 / mailbox : <ydtech@rd.netease.com>

As the scale of front-end projects continues to grow, multi-person collaborative development has become the standard for front-end development. What follows is that TypeScript is used by more and more projects. This change is not a blind pursuit of technology. , But the technological advancement driven by business. By providing strong type blessing to native JavaScript, TypeScript has greatly improved the code quality and greatly reduced the hidden bugs that may occur when different module interfaces are called mutually in multi-person collaboration scenarios. This series of sharing comes from some of my experience in learning and using TypeScript tools in my daily development. This series of articles is divided into three articles, the upper, the middle and the lower. Through this series of sharing, I hope that the following goals can be achieved:

  • Understand the implementation mechanism of each tool type from the perspective of source code
  • Learn the basic usage of each tool type through one or two simple examples
  • At the same time deepen the understanding of TypeScript
  • The final realization can be inferred in actual work

1. Partial<Type>: optional

1.1 Source code interpretation

The source code of Partial<Type> is as follows:

type Partial<T> = {
    [P in keyof T]?: T[P];
};

There are four points to be concerned about here:

  • <T> : This is the target type, that is, the type we have to deal with. The type is uncertain, so it is represented by the generic T
  • [P in keyof T] : keyof T returns a type composed of all keys of type T, in can be understood according to the for..in traversal in js, and keyof will be explained in more detail later
  • ? : Optional, all attributes of the return type are converted to optional types
  • What is returned is a new type, which is derived from T, and has a inheritance relationship , which will be verified in the description Required<Type> in Section 2

Based on the understanding of the source code, it can be well understood that the role of the Partial<Type> returns a new type, which has the same attributes as the target type T, but all attributes are optional .

1.2 Practical usage

Scenario description: In actual business development, we often encounter the need to do in whole or in part, and Partial<Type>

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

let store: DataModel = {
  name: '',
  age: 0,
  address: ''
}

function updateStore (
  store: DataModel,
  payload: Partial<DataModel>
):DataModel {
  return {
    ...store,
    ...payload
  }
}

store = updateStore(store, {
  name: 'lpp',
  age: 18
})
Try it

1.3 Supplement

Here is an explanation of keyof, and understand it through a piece of code:

interface Person {
  name: string;
  age: number;
  location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // "length" | "push" | "pop" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string

const person: Person = {
  name: '',
  age: 0,
  location: ''
}

type k11 = keyof typeof person; // "name" | "age" | "location"

Reference 1: Official document description

Reference 2: What does “keyof typeof” mean in TypeScript?

2. Required <Type>: necessary

2.1 Source code interpretation

type Required<T> = {
    [P in keyof T]-?: T[P];
};

The function of this type is to convert all attributes in type T to optional attributes .

A -? is used in the source code here to mark the attribute as a required attribute. Is this -? necessary? Because we understand that the optional attributes are used? The ones that are clearly marked are optional. If we remove -?, why can't we achieve the required effect? We first write a MyRequired<T>, as shown below:

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

interface Props {
  a?: number;
  b?: string;
}

const obj: MyRequired<Props> = { 
  a: 5
};
Try it

There is no type error in the above code, why? Because if it was just [P in keyof T], the attribute in P would be retain its own optionality in T . That is, if it was required before, it is still required in the new type. If it is optional, the same applies. Somewhat similar to a "inheritance relationship".
So use -? to clear optionality and implement Required. Of course, +? is also valid, so referring to Partial<T>, we can see that the +` of +? is can be omitted.

2.2 Practical usage

Required<T> will convert all the attributes of the incoming T type to necessary. So the most common usage is to do such conversions, but if you just want to some attributes of T type 1614064982e616 to required and return these attributes to a new type :

interface Props {
  a?: string
  b?: string
  c?: string
}

// 仅保留b,c属性并转为必填
type NewProps1 = Required<Pick<Props, 'b' | 'c'>>

// 需要保留Props的所有属性,但是b,c需要必填
type NewProps2 = Partial<Props> & Required<Pick<Props, 'b' | 'c'>>

const obj: NewProps2 = {
  b: '1',
  c: '2'
}
Try it

Three, ReadOnly<Type>: Read only

3.1 Source code interpretation

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

Set the attributes contained in the type T to readonly, and return a new type.
readonly, as the name implies, means read-only, and the value cannot be modified after initialization. This type can be used with JavaScript's const keyword to achieve the reference type attribute value constant .

This type has a limitation, that is, it can only set the sub-attribute to read-only. If the sub-attribute is still a reference type, it will not work for the grandchildren. So is there any way to achieve recursion and set all references to only Read it?

Welcome everyone to leave a message and discuss.

3.2 Practical usage

interface Person {
    name: string
    age: number
}

const person: Readonly<Person> = {
    name: 'lpp',
    age: 18
}

person.age = 20;    // 无法分配到 "age" ,因为它是只读属性。ts(2540)

If there is no readonly, in javascript, if you assign a unique reference type to a const variable, such as an object, you can modify the attribute value, but the reference stored in the variable cannot be modified. If you want to realize the immutability of the object attribute value, Object.freeze can be used in javascript.

function freeze<Type>(obj: Type): Readonly<Type>;

4. Record<Keys, Type>: Record

The tool type will construct a type, the key type of this type is Keys, and the value type is Type.

4.1 Source code interpretation

type Record<K extends keyof any, T> = {
    [P in K]: T;
};

Here we see that keyof any is used for the type definition of K. Here keyof any string | number | symbol, as shown below:

type a = keyof any;
// 等价于
type a = string | number | symbol;

4.2 Practical usage

// 简单的限定键和值的类型
type Obj1 = Record<string, string>

// 基于其他类型生成新的类型
type FruitTypes = 'apple' | 'banana' | 'pear'

interface FruitInfo {
  name: FruitTypes
  price: number
}

type Fruits = Partial<Record<FruitTypes, FruitInfo>>

const fruits: Fruits = {
  apple: {
    name: 'apple',
    price: 10
  }
}
Try it

5. Pick<Type, Keys>: Pick

From the Type, select a set of attributes to form a new type and return. This set of attributes is defined by Keys, which are strings or the union of strings.

5.1 Source code interpretation

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

K extends keyof , which means that K needs to be a subset of keyof T. The key of the returned type needs to satisfy [P in K], and the value type satisfies T[P].

5.2 Practical usage

interface Person {
  name: string
  age: number
  id: string
}

// 幼儿没有id
type Toddler = Pick<Person, 'name' | 'age'>

6. Omit<Type, Keys>: Ignore

Construct a type, this type contains the rest of the attributes of the type Type except Keys. Keys is a string or a union of strings.

6.1 Source code interpretation

type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Exclude<T, U> = T extends U ? never : T;

Because Omit relies on Exclude, the type source code of Exclude is posted here.
The function of Exclude<T, U> is to exclude those types that can be assigned to U from T.

I won't talk about how Exclude is implemented here, just know the function.

Therefore, Exclude<keyof T, K> can be regarded as an inverted selection. The attributes in T that are not included in K are selected, and then Pick is used to achieve Omit.

6.2 Practical usage

interface Person {
  name: string
  age: number
  id: string
  work: string
  address: string
  girlFriend: number
}

// 没工作的人
type PersonNoWork = Omit<Person, 'work'>

// 没住址的人
type PersonNoAddress = Omit<Person, 'address'>

// 没女朋友的人
type PersonNoGirlFriend =Omit<Person, 'girlFriend'

Seven, practice questions

How to implement a tool type SelectRequired<T, K in keyof T> to achieve the following effects:

interface Props {
  a?: string
  b?: string
  c?: string
  d: string
}

type NewProps = SelectRequired<Props, 'b' | 'c'>;    // { a?: string, b: string, c: string, d: string }
Click here for the answer

8. Next preview

In the next article "Fun with TypeScript Tool Type (Part 2)", the following content will be included, so stay tuned:

  • must read : extends conditional operator
  • Exclude<Type, ExcludeUnion>
  • Extract<Type, Union>
  • NonNullable
  • must read : tuple type tuple type
  • Parameters
  • ConstructorParameters
  • ReturnType
  • InstanceType

有道AI情报局
788 声望7.9k 粉丝