TypeScript Advanced Types

Original link: https://www.iyouhun.com/post-231.html

Type Compatibility in TS

TS Type Compatibility Reference Document

Two type systems: 1 Structural Type System 2 Nominal Type System

TS uses a structured type system, also known as duck typing, where type checking focuses on the shape of a value

That is, in a structural type system, two objects are considered to be of the same type if they have the same shape. for example:

 interface Point { x: number; y: number; } interface Point2D { x: number; y: number; } let p2: Point2D = { x: 1, y: 2, }; // 不会报错let p: Point = p2;

For the object type, the members of y are at least the same as x, then x is compatible with y ( more members can be assigned to less , or: as long as the necessary types are satisfied, more is fine)

 interface Point2D { x: number; y: number; } interface Point3D { x: number; y: number; z: number; } let p3: Point3D = { x: 1, y: 2, z: 3, }; // 不会报错let p2: Point2D = p3;

The type compatibility of function types is more complicated and needs to be considered: 1 number of parameters 2 return value type, etc.

  1. Number of parameters: more parameters are compatible with fewer parameters (in other words, less parameters can be assigned to more)

    • It is actually very common to omit unused function parameters in JS, and this way of use promotes compatibility between function types in TS
     const arr = ['a', 'b', 'c']; // arr.forEach 第一个参数的类型为: (value: string, index: number, array: string[]) => void arr.forEach(() => {}); arr.forEach((item) => {}); arr.forEach((item, index) => {}); type F1 = (a: number) => void; type F2 = (a: number, b: number) => void; // 正确:参数少的可以赋值给参数多的let f1: F1 = (a) => {}; let f2: F2 = f1;
  2. Return value type: as long as it meets the necessary type requirements, it’s okay to have more

     type F1 = () => void; const f1: F1 = () => { return 123; };

Generics overview

  • Generics can allow functions to work with multiple types under the premise of ensuring type safety, so as to achieve reuse , commonly used in: functions, interfaces, classes
  • Requirement: Create an id function, and return the data itself with whatever data is passed in (that is, the parameters and the return value type are the same)
 // 比如,该函数传入什么数值,就返回什么数值function id(value: number): number { return value; } // res => 10 const res = id(10);
  • For example, calling the above function with id(10) will directly return 10 itself. However, this function only accepts numeric types and cannot be used for other types
  • In order for the function to accept parameters of any type, the parameter type can be changed to any. However, in this way, the type protection of TS is lost, and the type is not safe
 function id(value: any): any { return value; }
  • At this point, you can use generics to achieve
  • While ensuring type safety (without losing type information), generics can allow functions, etc. to work with a variety of different types, which are flexible and reusable
  • In fact, in programming languages ​​such as C# and Java, generics are one of the main tools used to implement the functionality of reusable components

generic function

Create a generic function:

 function id<Type>(value: Type): Type { return value; } // 也可以仅使用一个字母来作为类型变量的名称function id<T>(value: T): T { return value; }

explain:

  • Syntax: add <> (angle brackets) after the function name , add type variables in the angle brackets, such as Type here
  • The type variable Type, is a special type of variable that deals with types rather than values
  • The type variable is equivalent to a type container , which can capture the type provided by the user (specifically what type is specified by the user when calling the function)
  • Because Type is a type, it can be used as the type of function parameter and return value, indicating that the parameter and return value have the same type
  • Type variable Type, which can be any legal variable name

Call the generic function:

 // 函数参数和返回值类型都为:number const num = id<number>(10); // 函数参数和返回值类型都为:string const str = id<string>('a');

explain:

  • Syntax: Add <> (angle brackets) after the function name, and specify the specific type in the angle brackets , for example, number here
  • When the type number is passed in, this type will be captured by the type variable Type specified when the function is declared.
  • At this time, the type of Type is number, so the type of function id parameter and return value are also number
  • In this way, through generics, the id function can work with a variety of different types, realizing reuse and ensuring type safety.

Simplify generic function calls

When calling a generic function, you can omit <类型> to simplify the calling of the generic function

 // 省略<number> 调用函数let num = id(10); let str = id('a');

explain:

  • At this time, a mechanism called type parameter inference will be used inside TS to automatically infer the type of the type variable Type according to the incoming actual parameters.
  • For example, when the actual parameter 10 is passed in, TS will automatically infer the type number of the variable num and use it as the type of Type
  • Recommended: use this simplified way of calling generic functions, making the code shorter and easier to read
  • Note: When the compiler cannot infer the type or the inferred type is inaccurate, you need to explicitly pass in the type parameter

generic constraints

By default, the type variable Type of a generic function can represent any type, which makes it impossible to access any properties

For example, the following sample code wants to get the length of the parameter:

  • Because Type can represent any type, there is no guarantee that the length attribute must exist. For example, the number type has no length. Therefore, the length property cannot be accessed
 function id<Type>(value: Type): Type { // 注意:此处会报错console.log(value.length); return value; } id('a');

At this point, you need to add constraints to the generic收缩类型(narrow the type value range)

There are two main ways to add a generic constraint contraction type: 1. Specify a more specific type 2. Add a constraint

First, let’s look at the first case, how to specify a more specific type:

For example, change the type to Type[] (an array of type Type), because as long as it is an array, there must be a length attribute, so it can be accessed

 function id<Type>(value: Type[]): Type[] { // 可以正确访问console.log(value.length); return value; }

Add generic constraints

 // 创建一个自定义类型interface ILength { length: number; } // Type extends ILength 添加泛型约束// 解释:表示传入的类型必须满足ILength 接口的要求才行,也就是得有一个number 类型的length 属性function id<Type extends ILength>(value: Type): Type { console.log(value.length); return value; }

explain:

  • Create an interface ILength describing constraints, which requires a length property
  • Add constraints to generics (type variables) through the extends keyword
  • This constraint means: the incoming type must have a length property
  • Note: The actual parameter passed in (for example, an array) only needs to have the length attribute (type compatibility)

Generics for multiple type variables

There can be multiple type variables of a generic type, and the type variables can also be constrained (for example, the second type variable is constrained by the first type variable) For example, create a function to get the value of the property in the object:

 function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) { return obj[key]; } let person = { name: 'jack', age: 18 }; getProp(person, 'name');

explain:

  1. A second type variable Key is added, and the two type variables are separated , commas.
  2. The keyof keyword takes an object type, yielding a union type of its key name (which may be a string or a number) .
  3. In this example, keyof Type actually obtains the union type of all keys of the person object, that is: 'name' | 'age'
  4. The type variable Key is constrained by Type, which can be understood as: Key can only be any one of all keys of Type, or can only access properties existing in the object
 // Type extends object 表示: Type 应该是一个对象类型,如果不是对象类型,就会报错// 如果要用到对象类型,应该用object ,而不是Object function getProperty<Type extends object, Key extends keyof Type>( obj: Type, key: Key, ) { return obj[key]; }

generic interface

Generic interface: Interface can also be used with generics to increase its flexibility and enhance its reusability

 interface IdFunc<Type> { id: (value: Type) => Type; ids: () => Type[]; } let obj: IdFunc<number> = { id(value) { return value; }, ids() { return [1, 3, 5]; }, };

explain:

  1. Add <类型变量> after the interface name, then the interface becomes a generic interface.
  2. The type variable of the interface is visible to all other members of the interface, that is , all members of the interface can use the type variable .
  3. When using a generic interface, you need to explicitly specify the concrete type (for example, IdFunc here).
  4. At this point, the parameter and return type of the id method are both number; the return type of the ids method is number[].

In fact, an array in JS is a generic interface in TS

 const strs = ['a', 'b', 'c']; // 鼠标放在forEach 上查看类型strs.forEach; const nums = [1, 3, 5]; // 鼠标放在forEach 上查看类型nums.forEach;
  • Explanation: When we are using an array, TS will automatically set the type variable to the corresponding type according to the different types of the array
  • Tip: You can view specific type information by pressing Ctrl + left mouse button (Mac: Command + left mouse button)

Generic tool type

Generic tool types: TS has built-in some common tool types to simplify some common operations in TS

Explanation: They are all implemented based on generics (generics are suitable for many types, more general), and are built-in and can be used directly in the code. There are many types of these tools, mainly to learn the following:

  1. Partial<Type>
  2. Readonly<Type>
  3. Pick<Type, Keys>

Partial

  • Partial is used to construct (create) a type, setting all properties of Type to optional.
 type Props = { id: string; children: number[]; }; type PartialProps = Partial<Props>;
  • Explanation: The constructed new type PartialProps has the same structure as Props, but all properties become optional.

Readonly

  • Readonly is used to construct a type, and all properties of Type are set to readonly (read-only).
 type Props = { id: string; children: number[]; }; type ReadonlyProps = Readonly<Props>;
  • Explanation: The constructed new type ReadonlyProps has the same structure as Props, but all properties become read-only.
 let props: ReadonlyProps = { id: '1', children: [] }; // 错误演示props.id = '2';
  • When we try to reassign the id property, we get an error: Cannot assign to “id” because it is a read-only property.

Pick

  • Pick selects a set of properties from Type to construct a new type. ,>
 interface Props { id: string; title: string; children: number[]; } type PickProps = Pick<Props, 'id' | 'title'>;
  • explain:
    1. The Pick tool type has two type variables: 1 indicates whose attribute is selected and 2 indicates which attributes are selected.
    2. For the second type variable, if only one is selected, only the attribute name can be passed in. If there are more than one, use the union type.
    3. The properties passed in by the second type variable can only be properties that exist in the first type variable.
    4. The constructed new type PickProps has only two property types: id and title.

This article is reprinted from: https://www.iyouhun.com/post-231.html
This site is for inclusion only, and the copyright belongs to the original author.

Leave a Comment

Your email address will not be published.